Fix and test jar processor caching.

This commit is contained in:
modmuss50
2023-01-23 22:06:05 +00:00
parent bab447ffe1
commit a1e671b719
19 changed files with 501 additions and 43 deletions

View File

@@ -42,5 +42,7 @@ public interface AccessWidenerEntry {
@Nullable
String mappingId();
String getSortKey();
void read(AccessWidenerVisitor visitor, LazyCloseable<TinyRemapper> remapper) throws IOException;
}

View File

@@ -24,10 +24,13 @@
package net.fabricmc.loom.configuration.accesswidener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
@@ -62,8 +65,14 @@ public class AccessWidenerJarProcessor implements MinecraftJarProcessor<AccessWi
List<AccessWidenerEntry> accessWideners = new ArrayList<>();
if (localAccessWidenerProperty.isPresent()) {
Path path = localAccessWidenerProperty.get().getAsFile().toPath();
if (Files.notExists(path)) {
throw new UncheckedIOException(new FileNotFoundException("Could not find access widener file at {%s}".formatted(path)));
}
// Add the access widener specified in the extension
accessWideners.add(new LocalAccessWidenerEntry(localAccessWidenerProperty.get().getAsFile().toPath()));
accessWideners.add(LocalAccessWidenerEntry.create(path));
}
/* Uncomment to read all access wideners from local mods.
@@ -84,7 +93,7 @@ public class AccessWidenerJarProcessor implements MinecraftJarProcessor<AccessWi
return null;
}
return new Spec(Collections.unmodifiableList(accessWideners));
return new Spec(accessWideners.stream().sorted(Comparator.comparing(AccessWidenerEntry::getSortKey)).toList());
}
@Override

View File

@@ -25,6 +25,7 @@
package net.fabricmc.loom.configuration.accesswidener;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -32,11 +33,20 @@ import org.jetbrains.annotations.Nullable;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerVisitor;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.LazyCloseable;
import net.fabricmc.loom.util.fmj.ModEnvironment;
import net.fabricmc.tinyremapper.TinyRemapper;
public record LocalAccessWidenerEntry(Path path) implements AccessWidenerEntry {
public record LocalAccessWidenerEntry(Path path, String hash) implements AccessWidenerEntry {
public static LocalAccessWidenerEntry create(Path path) {
try {
return new LocalAccessWidenerEntry(path, Checksum.sha1Hex(path));
} catch (IOException e) {
throw new UncheckedIOException("Failed to create LocalAccessWidenerEntry", e);
}
}
@Override
public void read(AccessWidenerVisitor visitor, LazyCloseable<TinyRemapper> remapper) throws IOException {
var reader = new AccessWidenerReader(visitor);
@@ -52,4 +62,14 @@ public record LocalAccessWidenerEntry(Path path) implements AccessWidenerEntry {
public @Nullable String mappingId() {
return null;
}
@Override
public String getSortKey() {
return "local";
}
@Override
public int hashCode() {
return hash.hashCode();
}
}

View File

@@ -61,6 +61,11 @@ public record ModAccessWidenerEntry(FabricModJson mod, String path, ModEnvironme
return transitiveOnly ? mod.getId() : null;
}
@Override
public String getSortKey() {
return mod.getId() + ":" + path;
}
@Override
public void read(AccessWidenerVisitor visitor, LazyCloseable<TinyRemapper> remapper) throws IOException {
if (transitiveOnly) {

View File

@@ -34,10 +34,13 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.processor.MappingProcessorContext;
@@ -49,6 +52,7 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class MinecraftJarProcessorManager {
private static final String CACHE_VALUE_FILE_PATH = "META-INF/Loom-Jar-Processor-Cache";
private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftJarProcessorManager.class);
private final List<ProcessorEntry<?>> jarProcessors;
@@ -73,14 +77,17 @@ public final class MinecraftJarProcessorManager {
List<ProcessorEntry<?>> entries = new ArrayList<>();
for (MinecraftJarProcessor<?> processor : processors) {
LOGGER.debug("Building processor spec for {}", processor.getName());
MinecraftJarProcessor.Spec spec = processor.buildSpec(context);
if (spec != null) {
LOGGER.debug("Adding processor entry for {}", processor.getName());
entries.add(new ProcessorEntry<>(processor, spec));
}
}
if (entries.isEmpty()) {
LOGGER.debug("No processor entries");
return null;
}
@@ -94,10 +101,23 @@ public final class MinecraftJarProcessorManager {
.collect(Collectors.joining("::"));
}
private String getDebugString() {
final StringJoiner sj = new StringJoiner("\n");
for (ProcessorEntry<?> jarProcessor : jarProcessors) {
sj.add(jarProcessor.name() + ":");
sj.add("\tHash: " + jarProcessor.hashCode());
sj.add("\tStr: " + jarProcessor.toString());
}
return sj.toString();
}
public boolean requiresProcessingJar(Path jar) {
Objects.requireNonNull(jar);
if (Files.notExists(jar)) {
LOGGER.debug("{} does not exist, generating", jar);
return true;
}
@@ -110,11 +130,23 @@ public final class MinecraftJarProcessorManager {
}
if (existingCache == null) {
LOGGER.info("{} does not contain a processor cache value, regenerating", jar);
return true;
}
final String existingCacheValue = new String(existingCache, StandardCharsets.UTF_8);
return !existingCacheValue.equals(getCacheValue());
final String expectedCacheValue = getCacheValue();
final boolean matches = existingCacheValue.equals(expectedCacheValue);
if (!matches) {
LOGGER.info("{} has an invalid cache, got {} expected {}", jar, existingCacheValue, expectedCacheValue);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Expected state: {}", getDebugString());
}
}
return !matches;
}
public void processJar(Path jar, ProcessorContext context) throws IOException {

View File

@@ -62,7 +62,7 @@ public class Checksum {
HashCode hash = Files.asByteSource(file).hash(Hashing.sha256());
return hash.asBytes();
} catch (IOException e) {
throw new RuntimeException("Failed to get file hash");
throw new UncheckedIOException("Failed to get file hash", e);
}
}

View File

@@ -33,8 +33,9 @@ import java.util.Objects;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJsonV1, FabricModJsonV2 {
public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJsonV1, FabricModJsonV2, FabricModJson.Mockable {
protected final JsonObject jsonObject;
private final FabricModJsonSource source;
@@ -59,4 +60,22 @@ public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJso
public final FabricModJsonSource getSource() {
return source;
}
@Override
public final String toString() {
return getClass().getName() + "[id=%s, version=%s, classTweakers=%s]".formatted(getId(), getVersion(), getClassTweakers());
}
@Override
public final int hashCode() {
return Objects.hash(getId(), getVersion());
}
@VisibleForTesting
public abstract non-sealed class Mockable extends FabricModJson {
private Mockable() {
super(null, null);
throw new AssertionError();
}
}
}

View File

@@ -24,17 +24,21 @@
package net.fabricmc.loom.util.fmj;
public enum ModEnvironment {
UNIVERSAL(true, true),
CLIENT(true, false),
SERVER(false, true);
import java.util.Objects;
public final class ModEnvironment {
public static final ModEnvironment UNIVERSAL = new ModEnvironment(true, true, "universal");
public static final ModEnvironment CLIENT = new ModEnvironment(true, false, "client");
public static final ModEnvironment SERVER = new ModEnvironment(false, true, "server");
private final boolean client;
private final boolean server;
private final String name;
ModEnvironment(boolean client, boolean server) {
private ModEnvironment(boolean client, boolean server, String name) {
this.client = client;
this.server = server;
this.name = name;
}
public boolean isClient() {
@@ -44,4 +48,17 @@ public enum ModEnvironment {
public boolean isServer() {
return server;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ModEnvironment that = (ModEnvironment) o;
return name.equals(that.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}

View File

@@ -26,16 +26,12 @@ package net.fabricmc.loom.test.unit
import net.fabricmc.loom.configuration.mods.ArtifactMetadata
import net.fabricmc.loom.configuration.mods.ArtifactRef
import net.fabricmc.loom.util.FileSystemUtil
import spock.lang.Specification
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.util.jar.Attributes
import java.util.jar.Manifest
import static net.fabricmc.loom.configuration.mods.ArtifactMetadata.RemapRequirements.*
import static net.fabricmc.loom.test.util.ZipTestUtils.*
class ArtifactMetadataTest extends Specification {
def "is fabric mod"() {
@@ -111,30 +107,4 @@ class ArtifactMetadataTest extends Specification {
private static ArtifactRef createArtifact(Path zip) {
return new ArtifactRef.FileArtifactRef(zip, "net.fabric", "loom-test", "1.0")
}
private static Path createZip(Map<String, String> entries) {
def file = Files.createTempFile("loom-test", ".zip")
Files.delete(file)
FileSystemUtil.getJarFileSystem(file, true).withCloseable { zip ->
entries.forEach { path, value ->
def fsPath = zip.getPath(path)
def fsPathParent = fsPath.getParent()
if (fsPathParent != null) Files.createDirectories(fsPathParent)
Files.writeString(fsPath, value, StandardCharsets.UTF_8)
}
}
return file
}
private String manifest(String key, String value) {
def manifest = new Manifest()
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes().putValue(key, value)
def out = new ByteArrayOutputStream()
manifest.write(out)
return out.toString(StandardCharsets.UTF_8)
}
}

View File

@@ -101,4 +101,13 @@ class FabricModJsonV0Test extends Specification {
then:
fmj.getClassTweakers() == [:]
}
def "hash code"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.hashCode() == 930565976
}
}

View File

@@ -107,4 +107,13 @@ class FabricModJsonV1Test extends Specification {
then:
fmj.getClassTweakers() == ["modid.accesswidener": ModEnvironment.UNIVERSAL]
}
def "hash code"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.hashCode() == 930565977
}
}

View File

@@ -125,4 +125,13 @@ class FabricModJsonV2Test extends Specification {
"universal.ct": ModEnvironment.UNIVERSAL
]
}
def "hash code"() {
given:
def mockSource = Mock(FabricModJsonSource)
when:
def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource)
then:
fmj.hashCode() == 930565978
}
}

View File

@@ -0,0 +1,92 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.processor
import net.fabricmc.loom.api.processor.SpecContext
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor
import net.fabricmc.loom.test.util.GradleTestUtil
import net.fabricmc.loom.util.fmj.FabricModJson
import net.fabricmc.loom.util.fmj.ModEnvironment
import spock.lang.Specification
class AccessWidenerJarProcessorTest extends Specification {
def "Local AW"() {
given:
def specContext = Mock(SpecContext)
def file = new File("src/test/resources/accesswidener/AccessWidenerJarProcessorTest.accesswidener")
def localAccessWidenerProperty = GradleTestUtil.mockRegularFileProperty(file)
def processor = new AccessWidenerJarProcessor("AccessWidener", true, localAccessWidenerProperty)
specContext.modDependencies() >> []
when:
def spec = processor.buildSpec(specContext)
then:
spec != null
spec.hashCode() == 1205905061
}
def "Dep AW"() {
given:
def specContext = Mock(SpecContext)
def mod1 = Mock(FabricModJson.Mockable)
mod1.getClassTweakers() >> ["test.accesswidener": ModEnvironment.UNIVERSAL]
mod1.getId() >> "modid1"
def mod2 = Mock(FabricModJson.Mockable)
mod2.getClassTweakers() >> ["test2.accesswidener": ModEnvironment.UNIVERSAL]
mod2.getId() >> "modid2"
specContext.modDependencies() >> [
mod1,
mod2
].shuffled()
def processor = new AccessWidenerJarProcessor("AccessWidener", true, GradleTestUtil.mockRegularFileProperty(null))
when:
def spec = processor.buildSpec(specContext)
then:
spec != null
spec.hashCode() == 1534839952
}
def "No AWs"() {
given:
def specContext = Mock(SpecContext)
specContext.modDependencies() >> []
def processor = new AccessWidenerJarProcessor("AccessWidener", true, GradleTestUtil.mockRegularFileProperty(null))
when:
def spec = processor.buildSpec(specContext)
then:
spec == null
}
}

View File

@@ -0,0 +1,70 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.processor
import net.fabricmc.loom.api.processor.ProcessorContext
import net.fabricmc.loom.api.processor.SpecContext
import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager
import net.fabricmc.loom.test.util.processor.TestMinecraftJarProcessor
import spock.lang.Specification
import static net.fabricmc.loom.test.util.ZipTestUtils.createZip
class MinecraftJarProcessorManagerTest extends Specification {
def "Does not require re-processing"() {
given:
def specContext = Mock(SpecContext)
def processorContext = Mock(ProcessorContext)
def processor1 = new TestMinecraftJarProcessor(input: "Test1")
def processor2 = new TestMinecraftJarProcessor(input: "Test2")
def manager = MinecraftJarProcessorManager.create([processor1, processor2], specContext)
when:
def jar = createZip(["fabric.mod.json": "{}"])
manager.processJar(jar, processorContext)
then:
!manager.requiresProcessingJar(jar)
}
def "Requires re-processing"() {
given:
def specContext = Mock(SpecContext)
def processorContext = Mock(ProcessorContext)
def processor1 = new TestMinecraftJarProcessor(input: "Test1")
def processor2 = new TestMinecraftJarProcessor(input: "Test2")
def manager1 = MinecraftJarProcessorManager.create([processor1], specContext)
def manager2 = MinecraftJarProcessorManager.create([processor1, processor2], specContext)
when:
def jar = createZip(["fabric.mod.json": "{}"])
manager1.processJar(jar, processorContext)
then:
manager2.requiresProcessingJar(jar)
}
}

View File

@@ -0,0 +1,50 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.processor
import net.fabricmc.loom.configuration.accesswidener.ModAccessWidenerEntry
import net.fabricmc.loom.util.fmj.FabricModJson
import net.fabricmc.loom.util.fmj.ModEnvironment
import spock.lang.Specification
class ModAccessWidenerEntryTest extends Specification {
def "read local mod"() {
given:
def mod = Mock(FabricModJson.Mockable)
mod.getClassTweakers() >> ["test.accesswidener": ModEnvironment.UNIVERSAL]
mod.hashCode() >> 0
when:
def entries = ModAccessWidenerEntry.readAll(mod, true)
then:
entries.size() == 1
def entry = entries[0]
entry.path() == "test.accesswidener"
entry.environment() == ModEnvironment.UNIVERSAL
entry.transitiveOnly()
entry.hashCode() == -1218981396
}
}

View File

@@ -26,6 +26,8 @@ package net.fabricmc.loom.test.util
import net.fabricmc.loom.LoomGradleExtension
import org.gradle.api.Project
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.tasks.DefaultSourceSet
import org.gradle.api.model.ObjectFactory
@@ -33,6 +35,7 @@ import org.gradle.api.plugins.ExtensionContainer
import org.gradle.api.provider.Property
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.util.PatternFilterable
import org.jetbrains.annotations.Nullable
import static org.mockito.ArgumentMatchers.any
import static org.mockito.Mockito.mock
@@ -90,4 +93,25 @@ class GradleTestUtil {
def mock = mock(PatternFilterable.class)
return mock
}
static RegularFile mockRegularFile(File file) {
def mock = mock(RegularFile.class)
when(mock.getAsFile()).thenReturn(file)
return mock
}
static RegularFileProperty mockRegularFileProperty(@Nullable File file) {
if (file == null) {
def mock = mock(RegularFileProperty.class)
when(mock.isPresent()).thenReturn(false)
return mock
}
def regularFile = mockRegularFile(file.getAbsoluteFile())
def mock = mock(RegularFileProperty.class)
when(mock.get()).thenReturn(regularFile)
when(mock.isPresent()).thenReturn(true)
return mock
}
}

View File

@@ -0,0 +1,61 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.util
import net.fabricmc.loom.util.FileSystemUtil
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.util.jar.Attributes
import java.util.jar.Manifest
class ZipTestUtils {
static Path createZip(Map<String, String> entries) {
def file = Files.createTempFile("loom-test", ".zip")
Files.delete(file)
FileSystemUtil.getJarFileSystem(file, true).withCloseable { zip ->
entries.forEach { path, value ->
def fsPath = zip.getPath(path)
def fsPathParent = fsPath.getParent()
if (fsPathParent != null) Files.createDirectories(fsPathParent)
Files.writeString(fsPath, value, StandardCharsets.UTF_8)
}
}
return file
}
static String manifest(String key, String value) {
def manifest = new Manifest()
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes().putValue(key, value)
def out = new ByteArrayOutputStream()
manifest.write(out)
return out.toString(StandardCharsets.UTF_8)
}
}

View File

@@ -0,0 +1,57 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.util.processor
import groovy.transform.Immutable
import net.fabricmc.loom.api.processor.MinecraftJarProcessor
import net.fabricmc.loom.api.processor.ProcessorContext
import net.fabricmc.loom.api.processor.SpecContext
import java.nio.file.Path
@Immutable
class TestMinecraftJarProcessor implements MinecraftJarProcessor<Spec> {
String input
final String name = "TestProcessor"
@Override
Spec buildSpec(SpecContext context) {
if (input == null) {
return null
}
return new Spec(input)
}
@Immutable
class Spec implements MinecraftJarProcessor.Spec {
String input
}
@Override
void processJar(Path jar, Spec spec, ProcessorContext context) throws IOException {
}
}

View File

@@ -0,0 +1,3 @@
accessWidener v2 named
transitive-accessible method net/minecraft/recipe/BrewingRecipeRegistry registerPotionType (Lnet/minecraft/item/Item;)V