From b69404ea03cbe70c610eb60b5d49500ded3bcb6e Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sat, 22 Jan 2022 23:29:51 +0000 Subject: [PATCH 1/8] Fix DecompilerOptions.getClasspath() I was blind and looking for this on the fork options... --- .../java/net/fabricmc/loom/task/GenerateSourcesTask.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 38e743fe..9c93e895 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -160,15 +160,17 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { private WorkQueue createWorkQueue(String jvmMarkerValue) { if (!useProcessIsolation()) { - return getWorkerExecutor().noIsolation(); + return getWorkerExecutor().classLoaderIsolation(spec -> { + spec.getClasspath().from(decompilerOptions.getClasspath()); + }); } return getWorkerExecutor().processIsolation(spec -> { spec.forkOptions(forkOptions -> { forkOptions.setMaxHeapSize("%dm".formatted(decompilerOptions.getMemory().get())); forkOptions.systemProperty(WorkerDaemonClientsManagerHelper.MARKER_PROP, jvmMarkerValue); - forkOptions.bootstrapClasspath(decompilerOptions.getClasspath()); }); + spec.getClasspath().from(decompilerOptions.getClasspath()); }); } From a5986251461daa91a940a80a0a7fff0c0f0e703a Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 23 Jan 2022 14:53:07 +0000 Subject: [PATCH 2/8] Make DecompilerOptions.getClasspath() an input for GenerateSourcesTask --- .../java/net/fabricmc/loom/task/GenerateSourcesTask.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 9c93e895..c76d41a8 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -45,6 +45,7 @@ import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.TaskAction; import org.gradle.workers.WorkAction; import org.gradle.workers.WorkParameters; @@ -84,6 +85,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @InputFile public abstract RegularFileProperty getRuntimeJar(); + @InputFiles + public abstract ConfigurableFileCollection getClasspath(); + @Inject public abstract WorkerExecutor getWorkerExecutor(); @@ -95,6 +99,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { this.decompilerOptions = decompilerOptions; getOutputs().upToDateWhen((o) -> false); + getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead(); } @TaskAction @@ -161,7 +166,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { private WorkQueue createWorkQueue(String jvmMarkerValue) { if (!useProcessIsolation()) { return getWorkerExecutor().classLoaderIsolation(spec -> { - spec.getClasspath().from(decompilerOptions.getClasspath()); + spec.getClasspath().from(getClasspath()); }); } @@ -170,7 +175,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { forkOptions.setMaxHeapSize("%dm".formatted(decompilerOptions.getMemory().get())); forkOptions.systemProperty(WorkerDaemonClientsManagerHelper.MARKER_PROP, jvmMarkerValue); }); - spec.getClasspath().from(decompilerOptions.getClasspath()); + spec.getClasspath().from(getClasspath()); }); } From a712954be270934855982f71e9f9d194deb2fa63 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 23 Jan 2022 18:26:56 +0000 Subject: [PATCH 3/8] Depend on decompilerOptions.getClasspath() build tasks. This doesn't feel necessary but seems to solve it. --- .../java/net/fabricmc/loom/task/GenerateSourcesTask.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index c76d41a8..d29fe3be 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -100,6 +100,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { getOutputs().upToDateWhen((o) -> false); getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead(); + dependsOn(decompilerOptions.getClasspath().getBuiltBy()); } @TaskAction @@ -328,8 +329,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { try { //noinspection unchecked return (Constructor) Class.forName(clazz).getConstructor(); - } catch (NoSuchMethodException | ClassNotFoundException e) { + } catch (NoSuchMethodException e) { return null; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); } } } From e4330a11dc86b3854232fb76d1037e2376239838 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 23 Jan 2022 20:06:26 +0000 Subject: [PATCH 4/8] Add custom decompiler tests. Includes test utils for creating buildSrc plugins. --- .../loom/test/LoomTestConstants.groovy | 2 +- .../test/integration/DecompileTest.groovy | 24 +++++++ .../decompile/CustomDecompiler.groovy | 37 ++++++++++ .../decompile/PreDecompileTask.groovy | 41 ++++++++++++ .../buildSrc/decompile/TestPlugin.groovy | 49 ++++++++++++++ .../test/util/GradleProjectTestTrait.groovy | 67 ++++++++++++++++++- 6 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/CustomDecompiler.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/PreDecompileTask.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/TestPlugin.groovy diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index 3b04a3e4..28bcc7f8 100644 --- a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy @@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion class LoomTestConstants { public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion() - public final static String PRE_RELEASE_GRADLE = "7.5-20220116010338+0000" + public final static String PRE_RELEASE_GRADLE = "7.5-20220122232041+0000" public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE] } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy index d2ed4b26..e4666f69 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy @@ -50,4 +50,28 @@ class DecompileTest extends Specification implements GradleProjectTestTrait { 'cfr' | "genSourcesWithCfr" | DEFAULT_GRADLE 'cfr' | "genSourcesWithCfr" | PRE_RELEASE_GRADLE } + + @Unroll + def "custom decompiler (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildSrc("decompile") + gradle.buildGradle << ''' + dependencies { + minecraft "com.mojang:minecraft:1.18.1" + mappings "net.fabricmc:yarn:1.18.1+build.18:v2" + } + ''' + when: + def result = gradle.run(task: "genSourcesWithCustom") + + then: + result.task(":genSourcesWithCustom").outcome == SUCCESS + result.task(":preDecompile").outcome == SUCCESS + result.output.contains("Writing test file") + result.output.contains("Running custom decompiler") + + where: + version << STANDARD_TEST_VERSIONS + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/CustomDecompiler.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/CustomDecompiler.groovy new file mode 100644 index 00000000..3c78ccfd --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/CustomDecompiler.groovy @@ -0,0 +1,37 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.integration.buildSrc.decompile + +import net.fabricmc.loom.api.decompilers.DecompilationMetadata +import net.fabricmc.loom.api.decompilers.LoomDecompiler + +import java.nio.file.Path + +class CustomDecompiler implements LoomDecompiler { + @Override + void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) { + println("Running custom decompiler") + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/PreDecompileTask.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/PreDecompileTask.groovy new file mode 100644 index 00000000..2232b142 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/PreDecompileTask.groovy @@ -0,0 +1,41 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.integration.buildSrc.decompile + +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction + +abstract class PreDecompileTask extends DefaultTask { + @OutputFile + abstract RegularFileProperty getOutputFile() + + @TaskAction + def run() { + println("Writing test file") + getOutputFile().asFile.get().text = "Test" + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/TestPlugin.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/TestPlugin.groovy new file mode 100644 index 00000000..e95302d0 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/decompile/TestPlugin.groovy @@ -0,0 +1,49 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.integration.buildSrc.decompile + +import net.fabricmc.loom.LoomGradleExtension +import org.gradle.api.Plugin +import org.gradle.api.Project + +class TestPlugin implements Plugin { + @Override + void apply(Project project) { + println("Test plugin") + def extension = LoomGradleExtension.get(project) + + def preDecompileTask = project.tasks.register("preDecompile", PreDecompileTask.class) { + outputFile = project.file("output.txt") + } + + extension.decompilerOptions.register("custom") { + decompilerClassName.set(CustomDecompiler.class.name) + + // BuiltBy shouldn't be required, but for some reason it is? Any ideas? + classpath.from(preDecompileTask) + classpath.builtBy(preDecompileTask) + } + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy index 9b108895..d47ffda7 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy @@ -27,6 +27,7 @@ package net.fabricmc.loom.test.util import groovy.transform.Immutable import net.fabricmc.loom.test.LoomTestConstants import net.fabricmc.loom.util.ZipUtils +import org.apache.commons.io.FileUtils import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner import spock.lang.Shared @@ -141,8 +142,8 @@ trait GradleProjectTestTrait { private String gradleVersion private String projectDir private String gradleHomeDir - private String warningMode + private boolean useBuildSrc BuildResult run(Map options) { // Setup the system props to tell loom that its running in a test env @@ -167,6 +168,10 @@ trait GradleProjectTestTrait { runner.withArguments(args as String[]) + if (useBuildSrc) { + writeBuildSrcDeps(runner) + } + return options.expectFailure ? runner.buildAndFail() : runner.build() } @@ -228,5 +233,65 @@ trait GradleProjectTestTrait { File getGeneratedSources(String mappings) { return new File(getGradleHomeDir(), "caches/fabric-loom/${mappings}/minecraft-merged-named-sources.jar") } + + void buildSrc(String name) { + useBuildSrc = true + + def buildSrcDir = new File(projectDir, "buildSrc") + buildSrcDir.mkdirs() + + def pluginClass = "net.fabricmc.loom.test.integration.buildSrc.${name}.TestPlugin" + new File(buildSrcDir, "build.gradle") << """ + plugins { + id 'groovy-gradle-plugin' + id 'groovy' + } + + gradlePlugin { + plugins { + simplePlugin { + id = 'loom-test-plugin' + implementationClass = '${pluginClass}' + } + } + } + """ + + new File(buildSrcDir, "settings.gradle") << ''' + rootProject.name='loom-test-plugin' + ''' + + // Patch the new plugin into the end of the plugins block + def matcher = buildGradle.text =~ /(?s)plugins \{(?.*)}/ + assert matcher.find() + def ids = matcher.group("ids") + + def pluginBlock = """ + plugins { + ${ids} + id 'loom-test-plugin' + } + """ + + buildGradle.text = buildGradle.text.replaceAll("(?s)(plugins \\{.*})", pluginBlock) + + def sourceSrc = new File("src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/" + name) + def targetSrc = new File(buildSrcDir, "src/main/groovy/net/fabricmc/loom/test/integration/buildSrc/" + name) + + FileUtils.copyDirectory(sourceSrc, targetSrc) + } + + void writeBuildSrcDeps(GradleRunner runner) { + def dependencies = "" + runner.pluginClasspath.forEach { File file -> + dependencies += "implementation files('${file.absolutePath.replace("\\", "\\\\")}')\n" + } + + new File(projectDir, "buildSrc/build.gradle") << """ + dependencies { + ${dependencies} + } + """ + } } } \ No newline at end of file From 9662a8b3de9bc15dd166ddf5d9b880e69a441fa5 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Mon, 24 Jan 2022 15:21:00 +0000 Subject: [PATCH 5/8] Support injecting interfaces from the mod source. Add a comment to target classes saying what mod is providing an injected interface. (#581) --- .../api/InterfaceInjectionExtensionAPI.java | 52 ++++++++ .../loom/api/LoomGradleExtensionAPI.java | 17 ++- .../configuration/CompileConfiguration.java | 4 +- ...nsitiveAccessWidenerMappingsProcessor.java | 46 +++---- .../InterfaceInjectionProcessor.java | 116 ++++++++++++++++-- .../extension/LoomGradleExtensionApiImpl.java | 32 +++-- .../loom/task/GenerateSourcesTask.java | 84 +++++++++---- .../dummyDependency/fabric.mod.json | 1 - .../src/main/java/ExampleMod.java | 1 + .../src/main/java/OwnInjectedInterface.java | 5 + .../src/main/resources/fabric.mod.json | 11 ++ 11 files changed, 285 insertions(+), 84 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/api/InterfaceInjectionExtensionAPI.java create mode 100644 src/test/resources/projects/interfaceInjection/src/main/java/OwnInjectedInterface.java create mode 100644 src/test/resources/projects/interfaceInjection/src/main/resources/fabric.mod.json diff --git a/src/main/java/net/fabricmc/loom/api/InterfaceInjectionExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/InterfaceInjectionExtensionAPI.java new file mode 100644 index 00000000..5a3cfbd3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/InterfaceInjectionExtensionAPI.java @@ -0,0 +1,52 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.api; + +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.SourceSet; + +public interface InterfaceInjectionExtensionAPI { + /** + * When true loom will inject interfaces declared in dependency mod manifests into the minecraft jar file. + * This is used to expose interfaces that are implemented on Minecraft classes by mixins at runtime + * in the dev environment. + * + * @return the property controlling interface injection. + */ + Property getEnableDependencyInterfaceInjection(); + + /** + * Contains a list of {@link SourceSet} that may contain a fabric.mod.json file with interfaces to inject. + * By default, this list contains only the main {@link SourceSet}. + * + * @return the list property containing the {@link SourceSet} + */ + ListProperty getInterfaceInjectionSourceSets(); + + default boolean isEnabled() { + return getEnableDependencyInterfaceInjection().get() || !getInterfaceInjectionSourceSets().get().isEmpty(); + } +} diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 6fc87212..f993e374 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -86,6 +86,12 @@ public interface LoomGradleExtensionAPI { // TODO: move this from LoomGradleExtensionAPI to LoomGradleExtension once getRefmapName & setRefmapName is removed. MixinExtensionAPI getMixin(); + default void interfaceInjection(Action action) { + action.execute(getInterfaceInjection()); + } + + InterfaceInjectionExtensionAPI getInterfaceInjection(); + Property getCustomMinecraftManifest(); /** @@ -131,15 +137,6 @@ public interface LoomGradleExtensionAPI { */ Property getIntermediaryUrl(); - /** - * When true loom will inject interfaces declared in mod manifests into the minecraft jar file. - * This is used to expose interfaces that are implemented on Minecraft classes by mixins at runtime - * in the dev environment. - * - * @return the property controlling interface injection. - */ - Property getEnableInterfaceInjection(); - @ApiStatus.Experimental Property getMinecraftJarConfiguration(); diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 9e6f859a..ee36469e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2016-2022 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 @@ -233,7 +233,7 @@ public final class CompileConfiguration { } } - if (extension.getEnableInterfaceInjection().get()) { + if (extension.getInterfaceInjection().isEnabled()) { InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(project); if (!jarProcessor.isEmpty()) { diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerMappingsProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerMappingsProcessor.java index 549200ab..e82ea83f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerMappingsProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerMappingsProcessor.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -24,58 +24,44 @@ package net.fabricmc.loom.configuration.accesswidener; -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.List; +import org.gradle.api.Project; import org.gradle.api.logging.Logger; import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerVisitor; import net.fabricmc.accesswidener.TransitiveOnlyFilter; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; -import net.fabricmc.mappingio.MappingReader; -import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; -import net.fabricmc.mappingio.format.Tiny2Writer; +import net.fabricmc.loom.task.GenerateSourcesTask; import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; -public final class TransitiveAccessWidenerMappingsProcessor { - private TransitiveAccessWidenerMappingsProcessor() { - } +public record TransitiveAccessWidenerMappingsProcessor(Project project) implements GenerateSourcesTask.MappingsProcessor { + @Override + public boolean transform(MemoryMappingTree mappings) { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + List accessWideners = extension.getTransitiveAccessWideners(); - public static void process(Path inputMappings, Path outputMappings, List accessWideners, Logger logger) { - MemoryMappingTree mappingTree = new MemoryMappingTree(); - - try (Reader reader = Files.newBufferedReader(inputMappings, StandardCharsets.UTF_8)) { - MappingReader.read(reader, new MappingSourceNsSwitch(mappingTree, MappingsNamespace.INTERMEDIARY.toString())); - } catch (IOException e) { - throw new RuntimeException("Failed to read mappings", e); + if (accessWideners.isEmpty()) { + return false; } - if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappingTree.getSrcNamespace())) { - throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappingTree.getSrcNamespace()); + if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappings.getSrcNamespace())) { + throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappings.getSrcNamespace()); } for (AccessWidenerFile accessWidener : accessWideners) { - MappingCommentVisitor mappingCommentVisitor = new MappingCommentVisitor(accessWidener.modId(), mappingTree, logger); + MappingCommentVisitor mappingCommentVisitor = new MappingCommentVisitor(accessWidener.modId(), mappings, project.getLogger()); AccessWidenerReader accessWidenerReader = new AccessWidenerReader(new TransitiveOnlyFilter(mappingCommentVisitor)); accessWidenerReader.read(accessWidener.content()); } - try (Writer writer = Files.newBufferedWriter(outputMappings, StandardCharsets.UTF_8)) { - Tiny2Writer tiny2Writer = new Tiny2Writer(writer, false); - mappingTree.accept(new MappingSourceNsSwitch(tiny2Writer, MappingsNamespace.NAMED.toString())); - } catch (IOException e) { - throw new RuntimeException("Failed to write mappings", e); - } + return true; } - private static record MappingCommentVisitor(String modId, MemoryMappingTree mappingTree, Logger logger) implements AccessWidenerVisitor { + private record MappingCommentVisitor(String modId, MemoryMappingTree mappingTree, Logger logger) implements AccessWidenerVisitor { @Override public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) { MappingTree.ClassMapping classMapping = mappingTree.getClass(name); diff --git a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java index bdf14739..bad920f4 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -26,7 +26,9 @@ package net.fabricmc.loom.configuration.ifaceinject; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -40,40 +42,47 @@ import java.util.stream.Collectors; import com.google.common.base.Preconditions; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; -import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.commons.Remapper; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.task.GenerateSourcesTask; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Pair; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.tinyremapper.TinyRemapper; -public class InterfaceInjectionProcessor implements JarProcessor { +public class InterfaceInjectionProcessor implements JarProcessor, GenerateSourcesTask.MappingsProcessor { // Filename used to store hash of injected interfaces in processed jar file private static final String HASH_FILENAME = "injected_interfaces.sha256"; private final Map> injectedInterfaces; private final Project project; private final LoomGradleExtension extension; + private final InterfaceInjectionExtensionAPI interfaceInjectionExtension; private final byte[] inputHash; private Map> remappedInjectedInterfaces; public InterfaceInjectionProcessor(Project project) { this.project = project; this.extension = LoomGradleExtension.get(project); + this.interfaceInjectionExtension = this.extension.getInterfaceInjection(); this.injectedInterfaces = getInjectedInterfaces().stream() .collect(Collectors.groupingBy(InjectedInterface::className)); @@ -158,6 +167,20 @@ public class InterfaceInjectionProcessor implements JarProcessor { private List getInjectedInterfaces() { List result = new ArrayList<>(); + if (interfaceInjectionExtension.getEnableDependencyInterfaceInjection().get()) { + result.addAll(getDependencyInjectedInterfaces()); + } + + for (SourceSet sourceSet : interfaceInjectionExtension.getInterfaceInjectionSourceSets().get()) { + result.addAll(getSourceInjectedInterface(sourceSet)); + } + + return result; + } + + private List getDependencyInjectedInterfaces() { + List result = new ArrayList<>(); + for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) { // Only apply injected interfaces from mods that are part of the compile classpath if (!entry.compileClasspath()) { @@ -176,12 +199,81 @@ public class InterfaceInjectionProcessor implements JarProcessor { return result; } + private List getSourceInjectedInterface(SourceSet sourceSet) { + final File fabricModJson; + + try { + fabricModJson = sourceSet.getResources() + .matching(patternFilterable -> patternFilterable.include("fabric.mod.json")) + .getSingleFile(); + } catch (IllegalStateException e) { + // File not found + return Collections.emptyList(); + } + + final String jsonString; + + try { + jsonString = Files.readString(fabricModJson.toPath(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read fabric.mod.json", e); + } + + final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(jsonString, JsonObject.class); + + return InjectedInterface.fromJson(jsonObject); + } + + @Override + public boolean transform(MemoryMappingTree mappings) { + if (injectedInterfaces.isEmpty()) { + return false; + } + + if (!MappingsNamespace.INTERMEDIARY.toString().equals(mappings.getSrcNamespace())) { + throw new IllegalStateException("Mapping tree must have intermediary src mappings not " + mappings.getSrcNamespace()); + } + + for (Map.Entry> entry : injectedInterfaces.entrySet()) { + final String className = entry.getKey(); + final List injectedInterfaces = entry.getValue(); + + MappingTree.ClassMapping classMapping = mappings.getClass(className); + + if (classMapping == null) { + final String modIds = injectedInterfaces.stream().map(InjectedInterface::modId).distinct().collect(Collectors.joining(",")); + project.getLogger().warn("Failed to find class ({}) to add injected interfaces from mod(s) ({})", className, modIds); + continue; + } + + classMapping.setComment(appendComment(classMapping.getComment(), injectedInterfaces)); + } + + return true; + } + + private static String appendComment(String comment, List injectedInterfaces) { + for (InjectedInterface injectedInterface : injectedInterfaces) { + String iiComment = "Interface {@link %s} injected by mod %s".formatted(injectedInterface.ifaceName.substring(injectedInterface.ifaceName.lastIndexOf("/") + 1), injectedInterface.modId); + + if (comment == null || !comment.contains(iiComment)) { + if (comment == null) { + comment = iiComment; + } else { + comment += "\n" + iiComment; + } + } + } + + return comment; + } + private record InjectedInterface(String modId, String className, String ifaceName) { /** * Reads the injected interfaces contained in a mod jar, or returns null if there is none. */ public static List fromModJar(Path modJarPath) { - byte[] modJsonBytes; + final byte[] modJsonBytes; try { modJsonBytes = ZipUtils.unpackNullable(modJarPath, "fabric.mod.json"); @@ -193,26 +285,30 @@ public class InterfaceInjectionProcessor implements JarProcessor { return Collections.emptyList(); } - JsonObject jsonObject = new Gson().fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); + final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class); - String modId = jsonObject.get("id").getAsString(); + return fromJson(jsonObject); + } + + public static List fromJson(JsonObject jsonObject) { + final String modId = jsonObject.get("id").getAsString(); if (!jsonObject.has("custom")) { return Collections.emptyList(); } - JsonObject custom = jsonObject.getAsJsonObject("custom"); + final JsonObject custom = jsonObject.getAsJsonObject("custom"); if (!custom.has("loom:injected_interfaces")) { return Collections.emptyList(); } - JsonObject addedIfaces = custom.getAsJsonObject("loom:injected_interfaces"); + final JsonObject addedIfaces = custom.getAsJsonObject("loom:injected_interfaces"); - List result = new ArrayList<>(); + final List result = new ArrayList<>(); for (String className : addedIfaces.keySet()) { - JsonArray ifaceNames = addedIfaces.getAsJsonArray(className); + final JsonArray ifaceNames = addedIfaces.getAsJsonArray(className); for (JsonElement ifaceName : ifaceNames) { result.add(new InjectedInterface(modId, className, ifaceName.getAsString())); diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index 09e4a633..02b23e2c 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -30,10 +30,13 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.tasks.SourceSet; +import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI; import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.MixinExtensionAPI; import net.fabricmc.loom.api.decompilers.DecompilerOptions; @@ -62,9 +65,9 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA protected final Property setupRemappedVariants; protected final Property transitiveAccessWideners; protected final Property intermediary; - protected final Property enableInterfaceInjection; private final Property runtimeOnlyLog4j; private final Property minecraftJarConfiguration; + private final InterfaceInjectionExtensionAPI interfaceInjectionExtension; private final ModVersionParser versionParser; @@ -88,9 +91,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.transitiveAccessWideners.finalizeValueOnRead(); this.intermediary = project.getObjects().property(String.class) .convention("https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar"); - this.enableInterfaceInjection = project.getObjects().property(Boolean.class) - .convention(true); - this.enableInterfaceInjection.finalizeValueOnRead(); this.versionParser = new ModVersionParser(project); @@ -108,6 +108,18 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.runtimeOnlyLog4j = project.getObjects().property(Boolean.class).convention(false); this.runtimeOnlyLog4j.finalizeValueOnRead(); + + this.interfaceInjectionExtension = project.getObjects().newInstance(InterfaceInjectionExtensionAPI.class); + + // Add main source set by default + interfaceInjection(interfaceInjection -> { + final JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); + final SourceSet main = javaPluginExtension.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); + interfaceInjection.getInterfaceInjectionSourceSets().add(main); + + interfaceInjection.getInterfaceInjectionSourceSets().finalizeValueOnRead(); + interfaceInjection.getEnableDependencyInterfaceInjection().convention(true).finalizeValueOnRead(); + }); } @Override @@ -202,11 +214,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return intermediary; } - @Override - public Property getEnableInterfaceInjection() { - return enableInterfaceInjection; - } - @Override public void disableDeprecatedPomGeneration(MavenPublication publication) { net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication); @@ -222,6 +229,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA return runtimeOnlyLog4j; } + @Override + public InterfaceInjectionExtensionAPI getInterfaceInjection() { + return interfaceInjectionExtension; + } + // This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods private final class EnsureCompile extends LoomGradleExtensionApiImpl { private EnsureCompile() { diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index d29fe3be..cb03b257 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2016-2022 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 @@ -26,12 +26,16 @@ package net.fabricmc.loom.task; import java.io.File; import java.io.IOException; +import java.io.Reader; import java.io.UncheckedIOException; +import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -57,8 +61,9 @@ import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.api.decompilers.DecompilationMetadata; import net.fabricmc.loom.api.decompilers.DecompilerOptions; import net.fabricmc.loom.api.decompilers.LoomDecompiler; -import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMappingsProcessor; +import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor; import net.fabricmc.loom.decompilers.LineNumberRemapper; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.FileSystemUtil; @@ -69,6 +74,10 @@ import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger; import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper; import net.fabricmc.loom.util.ipc.IPCClient; import net.fabricmc.loom.util.ipc.IPCServer; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; +import net.fabricmc.mappingio.format.Tiny2Writer; +import net.fabricmc.mappingio.tree.MemoryMappingTree; public abstract class GenerateSourcesTask extends AbstractLoomTask { private final DecompilerOptions decompilerOptions; @@ -300,29 +309,62 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } private Path getMappings() { - Path baseMappings = getExtension().getMappingsProvider().tinyMappings; + Path inputMappings = getExtension().getMappingsProvider().tinyMappings; - if (getExtension().getEnableTransitiveAccessWideners().get()) { - List accessWideners = getExtension().getTransitiveAccessWideners(); + MemoryMappingTree mappingTree = new MemoryMappingTree(); - if (accessWideners.isEmpty()) { - return baseMappings; - } - - Path outputMappings; - - try { - outputMappings = Files.createTempFile("loom-transitive-mappings", ".tiny"); - } catch (IOException e) { - throw new RuntimeException("Failed to create temp file", e); - } - - TransitiveAccessWidenerMappingsProcessor.process(baseMappings, outputMappings, accessWideners, getProject().getLogger()); - - return outputMappings; + try (Reader reader = Files.newBufferedReader(inputMappings, StandardCharsets.UTF_8)) { + MappingReader.read(reader, new MappingSourceNsSwitch(mappingTree, MappingsNamespace.INTERMEDIARY.toString())); + } catch (IOException e) { + throw new RuntimeException("Failed to read mappings", e); } - return baseMappings; + final List mappingsProcessors = new ArrayList<>(); + + if (getExtension().getEnableTransitiveAccessWideners().get()) { + mappingsProcessors.add(new TransitiveAccessWidenerMappingsProcessor(getProject())); + } + + if (getExtension().getInterfaceInjection().isEnabled()) { + mappingsProcessors.add(new InterfaceInjectionProcessor(getProject())); + } + + if (mappingsProcessors.isEmpty()) { + return inputMappings; + } + + boolean transformed = false; + + for (MappingsProcessor mappingsProcessor : mappingsProcessors) { + if (mappingsProcessor.transform(mappingTree)) { + transformed = true; + } + } + + if (!transformed) { + return inputMappings; + } + + final Path outputMappings; + + try { + outputMappings = Files.createTempFile("loom-transitive-mappings", ".tiny"); + } catch (IOException e) { + throw new RuntimeException("Failed to create temp file", e); + } + + try (Writer writer = Files.newBufferedWriter(outputMappings, StandardCharsets.UTF_8)) { + Tiny2Writer tiny2Writer = new Tiny2Writer(writer, false); + mappingTree.accept(new MappingSourceNsSwitch(tiny2Writer, MappingsNamespace.NAMED.toString())); + } catch (IOException e) { + throw new RuntimeException("Failed to write mappings", e); + } + + return outputMappings; + } + + public interface MappingsProcessor { + boolean transform(MemoryMappingTree mappings); } private static Constructor getDecompilerConstructor(String clazz) { diff --git a/src/test/resources/projects/interfaceInjection/dummyDependency/fabric.mod.json b/src/test/resources/projects/interfaceInjection/dummyDependency/fabric.mod.json index 0d61ca3a..66d3b71b 100644 --- a/src/test/resources/projects/interfaceInjection/dummyDependency/fabric.mod.json +++ b/src/test/resources/projects/interfaceInjection/dummyDependency/fabric.mod.json @@ -4,7 +4,6 @@ "version": "1", "name": "Dummy Mod", "custom": { - "fabric-api:module-lifecycle": "stable", "loom:injected_interfaces": { "net/minecraft/class_2248": ["InjectedInterface"] } diff --git a/src/test/resources/projects/interfaceInjection/src/main/java/ExampleMod.java b/src/test/resources/projects/interfaceInjection/src/main/java/ExampleMod.java index c73a730f..d808f6fc 100644 --- a/src/test/resources/projects/interfaceInjection/src/main/java/ExampleMod.java +++ b/src/test/resources/projects/interfaceInjection/src/main/java/ExampleMod.java @@ -6,5 +6,6 @@ public class ExampleMod implements ModInitializer { @Override public void onInitialize() { Blocks.AIR.newMethodThatDidNotExist(); + Blocks.AIR.anotherNewMethodThatDidNotExist(); } } diff --git a/src/test/resources/projects/interfaceInjection/src/main/java/OwnInjectedInterface.java b/src/test/resources/projects/interfaceInjection/src/main/java/OwnInjectedInterface.java new file mode 100644 index 00000000..4a96812d --- /dev/null +++ b/src/test/resources/projects/interfaceInjection/src/main/java/OwnInjectedInterface.java @@ -0,0 +1,5 @@ + +public interface OwnInjectedInterface { + default void anotherNewMethodThatDidNotExist() { + } +} diff --git a/src/test/resources/projects/interfaceInjection/src/main/resources/fabric.mod.json b/src/test/resources/projects/interfaceInjection/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..c5d78c1f --- /dev/null +++ b/src/test/resources/projects/interfaceInjection/src/main/resources/fabric.mod.json @@ -0,0 +1,11 @@ +{ + "schemaVersion": 1, + "id": "owndummy", + "version": "1", + "name": "Own Dummy Mod", + "custom": { + "loom:injected_interfaces": { + "net/minecraft/class_2248": ["OwnInjectedInterface"] + } + } +} From 46c6a2757aece1b303bd64e7c4a6ce8b2cea33ee Mon Sep 17 00:00:00 2001 From: Lovely_xianxian Date: Tue, 25 Jan 2022 03:05:55 +0800 Subject: [PATCH 6/8] Add support for mixins written in groovy (#580) --- .../mixin/AnnotationProcessorInvoker.java | 1 + .../loom/build/mixin/GroovyApInvoker.java | 64 +++++++++++++++++++ .../configuration/CompileConfiguration.java | 6 ++ 3 files changed, 71 insertions(+) create mode 100644 src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java diff --git a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java index b8ba4c39..cc8ae3f9 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -53,6 +53,7 @@ import net.fabricmc.loom.util.Constants; public abstract class AnnotationProcessorInvoker { public static final String JAVA = "java"; public static final String SCALA = "scala"; + public static final String GROOVY = "groovy"; protected final Project project; protected final Map invokerTasks; diff --git a/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java new file mode 100644 index 00000000..2f0d9623 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java @@ -0,0 +1,64 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2022 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.build.mixin; + +import java.io.File; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import org.gradle.api.Project; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.compile.GroovyCompile; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.extension.MixinExtension; + +public class GroovyApInvoker extends AnnotationProcessorInvoker { + public GroovyApInvoker(Project project) { + super( + project, + ImmutableList.of(), + getInvokerTasks(project)); + } + + private static Map getInvokerTasks(Project project) { + MixinExtension mixin = LoomGradleExtension.get(project).getMixin(); + return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.GROOVY).collect( + Collectors.toMap(Map.Entry::getKey, + entry -> Objects.requireNonNull((GroovyCompile) entry.getValue()))); + } + + @Override + protected void passArgument(GroovyCompile compileTask, String key, String value) { + compileTask.getOptions().getCompilerArgs().add("-A" + key + "=" + value); + } + + @Override + protected File getRefmapDestinationDir(GroovyCompile task) { + return task.getDestinationDirectory().getAsFile().get(); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index ee36469e..5f265430 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -37,6 +37,7 @@ import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.javadoc.Javadoc; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.build.mixin.GroovyApInvoker; import net.fabricmc.loom.build.mixin.JavaApInvoker; import net.fabricmc.loom.build.mixin.KaptApInvoker; import net.fabricmc.loom.build.mixin.ScalaApInvoker; @@ -269,6 +270,11 @@ public final class CompileConfiguration { project.getLogger().info("Configuring compiler arguments for Kapt plugin"); new KaptApInvoker(project).configureMixin(); } + + if (project.getPluginManager().hasPlugin("groovy")) { + project.getLogger().info("Configuring compiler arguments for Groovy"); + new GroovyApInvoker(project).configureMixin(); + } } private static void configureDecompileTasks(Project project) { From 316f9d95081c0d9ba5c8d8ee5db4fe80512ec5d7 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Mon, 24 Jan 2022 19:48:25 +0000 Subject: [PATCH 7/8] Update java-objc-bridge for arm64 MacOS support. Fixes the narrator. --- .../providers/minecraft/LWJGLVersionOverride.java | 6 ++++++ .../minecraft/MinecraftLibraryProvider.java | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java index 9471cafe..c3a6b2e9 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/LWJGLVersionOverride.java @@ -52,6 +52,12 @@ public class LWJGLVersionOverride { ); public static final List NATIVES = DEPENDENCIES.stream().map(s -> s + ":" + NATIVE_CLASSIFIER).toList(); + public static final List MACOS_DEPENDENCIES = List.of( + "ca.weblite:java-objc-bridge:1.1" + ); + // Same for now, as java-objc-bridge includes the natives in the main jar. + public static final List MACOS_NATIVES = MACOS_DEPENDENCIES; + /** * Update lwjgl by default when running on arm and a supported configuration. */ diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java index 97823109..e62785fe 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java @@ -33,6 +33,7 @@ import org.gradle.api.artifacts.ExternalModuleDependency; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.providers.BundleMetadata; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.OperatingSystem; public class MinecraftLibraryProvider { private static final Pattern NATIVES_PATTERN = Pattern.compile("^(?.*)/(.*?)/(?.*)/((?.*?)-([0-9].*?)-)(?.*).jar$"); @@ -45,6 +46,7 @@ public class MinecraftLibraryProvider { final boolean runtimeOnlyLog4j = extension.getRuntimeOnlyLog4j().get(); final boolean overrideLWJGL = LWJGLVersionOverride.overrideByDefault() || LWJGLVersionOverride.forceOverride(project) || Boolean.getBoolean("loom.test.lwjgloverride"); + final boolean isMacOS = OperatingSystem.CURRENT_OS.equals(OperatingSystem.MAC_OS); if (overrideLWJGL) { project.getLogger().warn("Loom is upgrading Minecraft's LWJGL version to {}", LWJGLVersionOverride.LWJGL_VERSION); @@ -86,6 +88,11 @@ public class MinecraftLibraryProvider { final String dependencyNotation = "%s:%s:%s:%s".formatted(group, name, version, classifier); + if (overrideLWJGL && isMacOS && "java-objc-bridge".equals(name)) { + // Mojang split out the natives into their own jar, skip over Mojang's jar and use the official jar later on. + continue; + } + project.getLogger().debug("Add native dependency '{}'", dependencyNotation); project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, dependencyNotation); } @@ -95,6 +102,11 @@ public class MinecraftLibraryProvider { LWJGLVersionOverride.DEPENDENCIES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, s)); LWJGLVersionOverride.NATIVES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, s)); + if (isMacOS) { + LWJGLVersionOverride.MACOS_DEPENDENCIES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, s)); + LWJGLVersionOverride.MACOS_NATIVES.forEach(s -> project.getDependencies().add(Constants.Configurations.MINECRAFT_NATIVES, s)); + } + // Add the native support mod that fixes a handful of issues related to the LWJGL update at runtime. ExternalModuleDependency dependency = (ExternalModuleDependency) project.getDependencies().create(Constants.Dependencies.NATIVE_SUPPORT + Constants.Dependencies.Versions.NATIVE_SUPPORT_VERSION); dependency.setTransitive(false); From d4be1e7bdca15a911da696d432dd0adc69fb0dde Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 25 Jan 2022 15:37:03 +0000 Subject: [PATCH 8/8] Fix issues resolving libraries and natives for old mc versions down to 1.3.2. (#584) * Fix issues resolving libraries and natives for old mc versions. Add a simple integration tests. Fixes #583 and #582 * Test 1.6.4 and 1.3.2 --- .../fabricmc/loom/LoomRepositoryPlugin.java | 12 ++++++ .../minecraft/MinecraftLibraryProvider.java | 19 +++++++-- .../loom/test/LoomTestConstants.groovy | 4 +- .../test/integration/DecompileTest.groovy | 4 +- .../test/integration/LegacyProjectTest.groovy | 41 ++++++++++++++++++- 5 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java index 1aed10a2..8b89642c 100644 --- a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java @@ -28,6 +28,7 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.dsl.RepositoryHandler; import org.gradle.api.artifacts.repositories.IvyArtifactRepository; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.initialization.Settings; import org.gradle.api.invocation.Gradle; import org.gradle.api.plugins.ExtensionAware; @@ -92,4 +93,15 @@ public class LoomRepositoryPlugin implements Plugin { repo.metadataSources(IvyArtifactRepository.MetadataSources::artifact); }); } + + public static void setupForLegacyVersions(Project project) { + // 1.4.7 contains an LWJGL version with an invalid maven pom, set the metadata sources to not use the pom for this version. + project.getRepositories().named("Mojang", MavenArtifactRepository.class, repo -> { + repo.metadataSources(sources -> { + // Only use the maven artifact and not the pom or gradle metadata. + sources.artifact(); + sources.ignoreGradleMetadataRedirection(); + }); + }); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java index e62785fe..f7834c69 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2018-2021 FabricMC + * Copyright (c) 2018-2022 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 @@ -31,12 +31,13 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.ExternalModuleDependency; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomRepositoryPlugin; import net.fabricmc.loom.configuration.providers.BundleMetadata; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.OperatingSystem; public class MinecraftLibraryProvider { - private static final Pattern NATIVES_PATTERN = Pattern.compile("^(?.*)/(.*?)/(?.*)/((?.*?)-([0-9].*?)-)(?.*).jar$"); + private static final Pattern NATIVES_PATTERN = Pattern.compile("^(?.*)/(.*?)/(?.*)/((?.*?)-(\\k)-)(?.*).jar$"); public void provide(MinecraftProvider minecraftProvider, Project project) { final LoomGradleExtension extension = LoomGradleExtension.get(project); @@ -58,6 +59,11 @@ public class MinecraftLibraryProvider { } if (library.isValidForOS() && !library.hasNatives() && library.artifact() != null) { + // 1.4.7 contains an LWJGL version with an invalid maven pom, set the metadata sources to not use the pom for this version. + if ("org.lwjgl.lwjgl:lwjgl:2.9.1-nightly-20130708-debug3".equals(library.name())) { + LoomRepositoryPlugin.setupForLegacyVersions(project); + } + if (runtimeOnlyLog4j && library.name().startsWith("org.apache.logging.log4j")) { // Make log4j a runtime only dep to force slf4j. project.getDependencies().add(Constants.Configurations.MINECRAFT_RUNTIME_DEPENDENCIES, library.name()); @@ -74,10 +80,15 @@ public class MinecraftLibraryProvider { if (library.hasNativesForOS()) { MinecraftVersionMeta.Download nativeDownload = library.classifierForOS(); - Matcher matcher = NATIVES_PATTERN.matcher(nativeDownload.path()); + if (nativeDownload == null) { + continue; + } + + final String path = nativeDownload.path(); + final Matcher matcher = NATIVES_PATTERN.matcher(path); if (!matcher.find()) { - project.getLogger().warn("Failed to match regex for natives path : " + nativeDownload.path()); + project.getLogger().warn("Failed to match regex for natives path : " + path); continue; } diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index 28bcc7f8..64ce9495 100644 --- a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2022 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 @@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion class LoomTestConstants { public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion() - public final static String PRE_RELEASE_GRADLE = "7.5-20220122232041+0000" + public final static String PRE_RELEASE_GRADLE = "7.5-20220124231322+0000" public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE] } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy index e4666f69..9e93097a 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DecompileTest.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2016-2022 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 @@ -45,9 +45,7 @@ class DecompileTest extends Specification implements GradleProjectTestTrait { where: decompiler | task | version - 'fernflower' | "genSourcesWithFernFlower" | DEFAULT_GRADLE 'fernflower' | "genSourcesWithFernFlower" | PRE_RELEASE_GRADLE - 'cfr' | "genSourcesWithCfr" | DEFAULT_GRADLE 'cfr' | "genSourcesWithCfr" | PRE_RELEASE_GRADLE } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/LegacyProjectTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/LegacyProjectTest.groovy index d8eeb66b..75259a17 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/LegacyProjectTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/LegacyProjectTest.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2018-2021 FabricMC + * Copyright (c) 2018-2022 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 @@ -28,7 +28,8 @@ import net.fabricmc.loom.test.util.GradleProjectTestTrait import spock.lang.Specification import spock.lang.Unroll -import static net.fabricmc.loom.test.LoomTestConstants.* +import static net.fabricmc.loom.test.LoomTestConstants.PRE_RELEASE_GRADLE +import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS import static org.gradle.testkit.runner.TaskOutcome.SUCCESS // This test uses gradle 4.9 and 1.14.4 v1 mappings @@ -47,4 +48,40 @@ class LegacyProjectTest extends Specification implements GradleProjectTestTrait where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "Unsupported minecraft (minecraft #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE) + gradle.buildGradle << """ + loom { + intermediaryUrl = 'https://s.modm.us/intermediary-empty-v2.jar' + } + + dependencies { + minecraft "com.mojang:minecraft:${version}" + mappings loom.layered() { + // No names + } + + modImplementation "net.fabricmc:fabric-loader:0.12.12" + } + """ + + when: + def result = gradle.run(task: "configureClientLaunch") + + then: + result.task(":configureClientLaunch").outcome == SUCCESS + + where: + version | _ + '1.13.2' | _ + '1.12.2' | _ + '1.8.9' | _ + '1.7.10' | _ + '1.6.4' | _ + '1.4.7' | _ + '1.3.2' | _ + } }