diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 88e22bda..1ce19c5b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ gson = "2.10.1" guava = "33.0.0-jre" stitch = "0.6.2" -tiny-remapper = "0.10.3" +tiny-remapper = "0.10.4" access-widener = "2.1.0" mapping-io = "0.6.1" lorenz-tiny = "4.0.2" diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml index 560de2a7..342a0c0e 100644 --- a/gradle/runtime.libs.versions.toml +++ b/gradle/runtime.libs.versions.toml @@ -8,7 +8,7 @@ vineflower = "1.10.1" mixin-compile-extensions = "0.6.0" dev-launch-injector = "0.2.1+build.8" terminal-console-appender = "1.3.0" -jetbrains-annotations = "24.1.0" +jetbrains-annotations = "25.0.0" native-support = "1.0.1" # Forge Runtime depedencies diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index 1dfbf935..c3c92e8b 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -1,13 +1,13 @@ [versions] spock = "2.3-groovy-3.0" -junit = "5.10.2" -javalin = "6.1.6" -mockito = "5.12.0" +junit = "5.11.1" +javalin = "6.3.0" +mockito = "5.13.0" java-debug = "0.52.0" -mixin = "0.12.5+mixin.0.8.5" +mixin = "0.15.3+mixin.0.8.7" -gradle-nightly = "8.11-20240814172604+0000" -fabric-loader = "0.15.11" +gradle-nightly = "8.11-20240926001708+0000" +fabric-loader = "0.16.5" fabric-installer = "1.0.1" [libraries] diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index a1f12033..c53f5c74 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -57,7 +57,6 @@ import net.fabricmc.loom.configuration.providers.minecraft.mapped.MojangMappedMi import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider; import net.fabricmc.loom.extension.LoomFiles; -import net.fabricmc.loom.extension.LoomProblemReporter; import net.fabricmc.loom.extension.MixinExtension; import net.fabricmc.loom.extension.RemapperExtensionHolder; import net.fabricmc.loom.util.ModPlatform; @@ -147,8 +146,6 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { Collection getLayeredMappingFactories(); - LoomProblemReporter getProblemReporter(); - boolean isConfigurationCacheActive(); // =================== diff --git a/src/main/java/net/fabricmc/loom/configuration/DependencyInfo.java b/src/main/java/net/fabricmc/loom/configuration/DependencyInfo.java index bef7b526..3ded75b1 100644 --- a/src/main/java/net/fabricmc/loom/configuration/DependencyInfo.java +++ b/src/main/java/net/fabricmc/loom/configuration/DependencyInfo.java @@ -37,9 +37,6 @@ import org.gradle.api.artifacts.ResolvedDependency; import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.util.gradle.SelfResolvingDependencyUtils; - public class DependencyInfo { final Project project; final Dependency dependency; @@ -66,10 +63,7 @@ public class DependencyInfo { } public static DependencyInfo create(Project project, Dependency dependency, Configuration sourceConfiguration) { - if (SelfResolvingDependencyUtils.isExplicitSRD(dependency)) { - LoomGradleExtension.get(project).getProblemReporter().reportSelfResolvingDependencyUsage(); - return FileDependencyInfo.createForDeprecatedSRD(project, dependency, sourceConfiguration); - } else if (dependency instanceof FileCollectionDependency fileCollectionDependency) { + if (dependency instanceof FileCollectionDependency fileCollectionDependency) { return new FileDependencyInfo(project, fileCollectionDependency, sourceConfiguration); } else { return new DependencyInfo(project, dependency, sourceConfiguration); diff --git a/src/main/java/net/fabricmc/loom/configuration/FileDependencyInfo.java b/src/main/java/net/fabricmc/loom/configuration/FileDependencyInfo.java index cb8092ad..2434ec05 100644 --- a/src/main/java/net/fabricmc/loom/configuration/FileDependencyInfo.java +++ b/src/main/java/net/fabricmc/loom/configuration/FileDependencyInfo.java @@ -46,7 +46,6 @@ import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.FileCollectionDependency; import net.fabricmc.loom.util.ZipUtils; -import net.fabricmc.loom.util.gradle.SelfResolvingDependencyUtils; public class FileDependencyInfo extends DependencyInfo { protected final Map classifierToFile = new HashMap<>(); @@ -152,15 +151,6 @@ public class FileDependencyInfo extends DependencyInfo { } } - @Deprecated // Remove in Gradle 9 - public static FileDependencyInfo createForDeprecatedSRD(Project project, Dependency dependency, Configuration configuration) { - if (!SelfResolvingDependencyUtils.isExplicitSRD(dependency)) { - throw new IllegalArgumentException("Dependency is a FileCollectionDependency"); - } - - return new FileDependencyInfo(project, dependency, configuration, SelfResolvingDependencyUtils.resolve(dependency)); - } - @Override public String getResolvedVersion() { return version; 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 5b2415c8..7ab263d6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java @@ -247,7 +247,7 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess name = ifaceInfo.substring(0, ifaceInfo.indexOf("<")); generics = ifaceInfo.substring(ifaceInfo.indexOf("<")); - // First Generics Check, if there are generics, are them correctly written? + // First Generics Check, if there are generics, are they correctly written? SignatureReader reader = new SignatureReader("Ljava/lang/Object" + generics + ";"); CheckSignatureAdapter checker = new CheckSignatureAdapter(CheckSignatureAdapter.CLASS_SIGNATURE, null); reader.accept(checker); @@ -318,6 +318,7 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess // Second Generics Check, if there are passed generics, are all of them present in the target class? GenericsChecker checker = new GenericsChecker(Constants.ASM_VERSION, injectedInterfaces); reader.accept(checker); + checker.check(); var resultingSignature = new StringBuilder(signature); @@ -350,7 +351,7 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess @Override public void visitEnd() { // inject any necessary inner class entries - // this may produce technically incorrect bytecode cuz we don't know the actual access flags for inner class entries + // this may produce technically incorrect bytecode cuz we don't know the actual access flags for inner class entries, // but it's hopefully enough to quiet some IDE errors for (final InjectedInterface itf : injectedInterfaces) { if (this.knownInnerClasses.contains(itf.ifaceName())) { @@ -407,8 +408,8 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess super.visitFormalTypeParameter(name); } - @Override - public void visitEnd() { + // Ensures that injected interfaces only use collected type parameters from the target class + public void check() { for (InjectedInterface injectedInterface : this.injectedInterfaces) { if (injectedInterface.generics() != null) { SignatureReader reader = new SignatureReader("Ljava/lang/Object" + injectedInterface.generics() + ";"); @@ -421,8 +422,6 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess reader.accept(confirm); } } - - super.visitEnd(); } public static class GenericsConfirm extends SignatureVisitor { diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java b/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java index 97e96a47..18826346 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java @@ -166,7 +166,7 @@ public record SpecContextImpl(List modDependencies, List alternatives; + private int testNsId; + private int[] alternativesMapping; + + private String srcName; + private String[] dstNames; + private boolean[] unobf; + private boolean[] lastMethodUnobf; + + private boolean relayHeaderOrMetadata; + + public UnobfuscatedMappingNsCompleter(MappingVisitor next, String testNs, Map alternatives) { + super(next); + + this.testNs = testNs; + this.alternatives = alternatives; + } + + @Override + public boolean visitHeader() throws IOException { + relayHeaderOrMetadata = next.visitHeader(); + + return true; + } + + @Override + public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException { + int count = dstNamespaces.size(); + testNsId = -1; + alternativesMapping = new int[count]; + dstNames = new String[count]; + unobf = new boolean[count + 1]; // contains src ns as well + lastMethodUnobf = new boolean[count + 1]; // contains src ns as well + + for (int i = 0; i < count; i++) { + String dst = dstNamespaces.get(i); + + if (testNs.equals(dst)) { + testNsId = i; + } + + String src = alternatives.get(dst); + int srcIdx; + + if (src == null) { + srcIdx = i; + } else if (src.equals(srcNamespace)) { + srcIdx = -1; + } else { + srcIdx = dstNamespaces.indexOf(src); + if (srcIdx < 0) throw new RuntimeException("invalid alternative mapping ns "+src+": not in "+dstNamespaces+" or "+srcNamespace); + } + + alternativesMapping[i] = srcIdx; + } + + if (testNsId == -1 && !testNs.equals(srcNamespace)) throw new RuntimeException("test namespace " + testNs + " not present in src and dst namespaces!"); + + if (relayHeaderOrMetadata) next.visitNamespaces(srcNamespace, dstNamespaces); + } + + @Override + public void visitMetadata(String key, @Nullable String value) throws IOException { + if (relayHeaderOrMetadata) next.visitMetadata(key, value); + } + + @Override + public boolean visitContent() throws IOException { + relayHeaderOrMetadata = true; // for in-content metadata + + return next.visitContent(); + } + + @Override + public boolean visitClass(String srcName) throws IOException { + this.srcName = srcName; + + return next.visitClass(srcName); + } + + @Override + public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException { + this.srcName = srcName; + + return next.visitField(srcName, srcDesc); + } + + @Override + public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException { + this.srcName = srcName; + + return next.visitMethod(srcName, srcDesc); + } + + @Override + public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException { + this.srcName = srcName; + + return next.visitMethodArg(argPosition, lvIndex, srcName); + } + + @Override + public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) throws IOException { + this.srcName = srcName; + + return next.visitMethodVar(lvtRowIndex, lvIndex, startOpIdx, endOpIdx, srcName); + } + + @Override + public void visitDstName(MappedElementKind targetKind, int namespace, String name) { + dstNames[namespace] = name; + } + + @Override + public boolean visitElementContent(MappedElementKind targetKind) throws IOException { + for (int ns : alternativesMapping) { + int idx = ns + 1; // offset by 1 bc src ns is -1 + + if (targetKind == MappedElementKind.METHOD_ARG || targetKind == MappedElementKind.METHOD_VAR) { + unobf[idx] = lastMethodUnobf[idx]; + } else if (ns == testNsId) { + unobf[idx] = true; + + if (targetKind == MappedElementKind.METHOD) { + lastMethodUnobf[idx] = true; + } + } else if (!unobf[idx]) { // only check each ns once + String name = ns == -1 ? srcName : dstNames[ns]; + String testName = dstNames[testNsId]; + + if (testName != null && testName.equals(name)) { + unobf[idx] = true; + + if (targetKind == MappedElementKind.METHOD) { + lastMethodUnobf[idx] = true; + } + } + } + } + + nsLoop: for (int i = 0; i < dstNames.length; i++) { + String name = dstNames[i]; + + if (name == null) { + int src = i; + long visited = 1L << src; + + do { + int newSrc = alternativesMapping[src]; + + if (newSrc < 0) { // mapping to src name + if (unobf[newSrc + 1]) { + name = srcName; + break; // srcName must never be null + } else { + continue nsLoop; + } + } else if (newSrc == src) { // no-op (identity) mapping, explicit in case src > 64 + continue nsLoop; // always null + } else if ((visited & 1L << newSrc) != 0) { // cyclic mapping + continue nsLoop; // always null + } else { + if (unobf[newSrc + 1]) { + src = newSrc; + name = dstNames[src]; + visited |= 1L << src; + } else { + continue nsLoop; + } + } + } while (name == null); + + assert name != null; + } + + next.visitDstName(targetKind, i, name); + } + + Arrays.fill(dstNames, null); + Arrays.fill(unobf, false); + Arrays.fill(lastMethodUnobf, false); + + return next.visitElementContent(targetKind); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/utils/DependencyFileSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/utils/DependencyFileSpec.java index 0eac2fe5..5cd959c7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/utils/DependencyFileSpec.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/utils/DependencyFileSpec.java @@ -34,27 +34,11 @@ import org.gradle.api.artifacts.FileCollectionDependency; import net.fabricmc.loom.api.mappings.layered.MappingContext; import net.fabricmc.loom.api.mappings.layered.spec.FileSpec; -import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext; -import net.fabricmc.loom.util.gradle.SelfResolvingDependencyUtils; public record DependencyFileSpec(Dependency dependency) implements FileSpec { @Override public Path get(MappingContext context) { - if (SelfResolvingDependencyUtils.isExplicitSRD(dependency)) { - if (context instanceof GradleMappingContext gradleMappingContext) { - gradleMappingContext.getExtension().getProblemReporter().reportSelfResolvingDependencyUsage(); - } - - Set files = SelfResolvingDependencyUtils.resolve(dependency); - - if (files.isEmpty()) { - throw new RuntimeException("SelfResolvingDependency (%s) resolved no files".formatted(dependency.toString())); - } else if (files.size() > 1) { - throw new RuntimeException("SelfResolvingDependency (%s) resolved too many files (%d) only 1 is expected".formatted(dependency.toString(), files.size())); - } - - return files.iterator().next().toPath(); - } else if (dependency instanceof FileCollectionDependency fileCollectionDependency) { + if (dependency instanceof FileCollectionDependency fileCollectionDependency) { Set files = fileCollectionDependency.getFiles().getFiles(); if (files.isEmpty()) { 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 896a2bd5..369083ae 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 @@ -119,10 +119,23 @@ public class MinecraftLibraryProvider { } private List processLibraries(List libraries) { - final LibraryContext libraryContext = new LibraryContext(minecraftProvider.getVersionInfo(), JavaVersion.current()); + final LibraryContext libraryContext = new LibraryContext(minecraftProvider.getVersionInfo(), getTargetRuntimeJavaVersion()); return processorManager.processLibraries(libraries, libraryContext); } + private JavaVersion getTargetRuntimeJavaVersion() { + final Object property = project.findProperty(Constants.Properties.RUNTIME_JAVA_COMPATIBILITY_VERSION); + + if (property != null) { + // This is very much a last ditch effort to allow users to set the runtime java version + // It's not recommended and will likely cause support confusion if it has been changed without good reason. + project.getLogger().warn("Runtime java compatibility version has manually been set to: %s".formatted(property)); + return JavaVersion.toVersion(property); + } + + return JavaVersion.current(); + } + private void applyClientLibrary(Library library) { switch (library.target()) { case COMPILE -> addLibrary(Constants.Configurations.MINECRAFT_CLIENT_COMPILE_LIBRARIES, library); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java index c14e2099..2ec6fbb7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftSourceSets.java @@ -209,6 +209,12 @@ public abstract sealed class MinecraftSourceSets permits MinecraftSourceSets.Sin extendsFrom(project, clientOnlySourceSet.getCompileClasspathConfigurationName(), mainSourceSet.getCompileClasspathConfigurationName()); extendsFrom(project, clientOnlySourceSet.getRuntimeClasspathConfigurationName(), mainSourceSet.getRuntimeClasspathConfigurationName()); + // Test source set depends on client + final SourceSet testSourceSet = SourceSetHelper.getSourceSetByName(SourceSet.TEST_SOURCE_SET_NAME, project); + extendsFrom(project, testSourceSet.getCompileClasspathConfigurationName(), clientOnlySourceSet.getCompileClasspathConfigurationName()); + extendsFrom(project, testSourceSet.getRuntimeClasspathConfigurationName(), clientOnlySourceSet.getRuntimeClasspathConfigurationName()); + project.getDependencies().add(testSourceSet.getImplementationConfigurationName(), clientOnlySourceSet.getOutput()); + RemapConfigurations.configureClientConfigurations(project, clientOnlySourceSet); // Include the client only output in the jars diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java index 0e247360..5909171f 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java @@ -84,7 +84,6 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl private InstallerData installerData; private boolean refreshDeps; private final ListProperty libraryProcessorFactories; - private final LoomProblemReporter problemReporter; private final boolean configurationCacheActive; private final boolean isolatedProjectsActive; @@ -127,15 +126,9 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl configurationCacheActive = getBuildFeatures().getConfigurationCache().getActive().get(); isolatedProjectsActive = getBuildFeatures().getIsolatedProjects().getActive().get(); - if (configurationCacheActive) { - project.getLogger().warn("Loom support for the Gradle configuration cache is highly experimental and may not work as expected. Please report any issues you encounter."); - } - if (refreshDeps) { project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower."); } - - problemReporter = project.getObjects().newInstance(LoomProblemReporter.class); } @Override @@ -329,11 +322,6 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl provider.getIsLegacyMinecraft().disallowChanges(); } - @Override - public LoomProblemReporter getProblemReporter() { - return problemReporter; - } - @Override public boolean isConfigurationCacheActive() { return configurationCacheActive; diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index a577b9c5..61f651b4 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -241,6 +241,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { getResetCache().convention(getExtension().refreshDeps()); getMappings().set(SourceMappingsService.create(getProject())); + + mustRunAfter(getProject().getTasks().withType(AbstractRemapJarTask.class)); } @TaskAction diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index 01107a19..3394fc10 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -161,6 +161,11 @@ public class Constants { public static final String LIBRARY_PROCESSORS = "fabric.loom.libraryProcessors"; @ApiStatus.Experimental public static final String SANDBOX = "fabric.loom.experimental.sandbox"; + /** + * When set the version of java that will be assumed that the game will run on, this defaults to the current java version. + * Only set this when you have a good reason to do so, the default should be fine for almost all cases. + */ + public static final String RUNTIME_JAVA_COMPATIBILITY_VERSION = "fabric.loom.runtimeJavaCompatibilityVersion"; public static final String ALLOW_MISMATCHED_PLATFORM_VERSION = "loom.allowMismatchedPlatformVersion"; public static final String IGNORE_DEPENDENCY_LOOM_VERSION_VALIDATION = "loom.ignoreDependencyLoomVersionValidation"; } diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index 72b2ca88..4f79888f 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -133,22 +133,51 @@ public final class TinyRemapperHelper { for (MappingTree.ClassMapping classDef : mappings.getClasses()) { String className = classDef.getName(fromId); - String dstName = classDef.getName(toId); - if (dstName == null) { - // Unsure if this is correct, should be better than crashing tho. - dstName = className; + if (className == null) { + continue; } - acceptor.acceptClass(className, dstName); + String dstClassName = classDef.getName(toId); + + if (dstClassName == null) { + // Unsure if this is correct, should be better than crashing tho. + dstClassName = className; + } + + acceptor.acceptClass(className, dstClassName); for (MappingTree.FieldMapping field : classDef.getFields()) { - acceptor.acceptField(memberOf(className, field.getName(fromId), field.getDesc(fromId)), field.getName(toId)); + String fieldName = field.getName(fromId); + + if (fieldName == null) { + continue; + } + + String dstFieldName = field.getName(toId); + + if (dstFieldName == null) { + dstFieldName = fieldName; + } + + acceptor.acceptField(memberOf(className, fieldName, field.getDesc(fromId)), dstFieldName); } for (MappingTree.MethodMapping method : classDef.getMethods()) { - IMappingProvider.Member methodIdentifier = memberOf(className, method.getName(fromId), method.getDesc(fromId)); - acceptor.acceptMethod(methodIdentifier, method.getName(toId)); + String methodName = method.getName(fromId); + + if (methodName == null) { + continue; + } + + String dstMethodName = method.getName(toId); + + if (dstMethodName == null) { + dstMethodName = methodName; + } + + IMappingProvider.Member methodIdentifier = memberOf(className, methodName, method.getDesc(fromId)); + acceptor.acceptMethod(methodIdentifier, dstMethodName); if (remapLocalVariables) { for (MappingTree.MethodArgMapping parameter : method.getArgs()) { diff --git a/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java b/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java index 86a44434..bcd96d3b 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java @@ -25,10 +25,13 @@ package net.fabricmc.loom.util.gradle; import java.io.File; +import java.lang.reflect.Field; import java.util.function.Consumer; import org.gradle.api.Project; +import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency; import org.gradle.api.invocation.Gradle; import org.gradle.api.provider.Provider; @@ -90,4 +93,18 @@ public final class GradleUtils { property.set(file); return property.getAsFile().get(); } + + // Get the project from the field with reflection to suppress the deprecation warning. + // If you hate it find a solution yourself and make a PR, I'm getting a bit tired of chasing Gradle updates + public static Project getDependencyProject(ProjectDependency projectDependency) { + try { + final Class clazz = DefaultProjectDependency.class; + final Field dependencyProject = clazz.getDeclaredField("dependencyProject"); + dependencyProject.setAccessible(true); + return (Project) dependencyProject.get(projectDependency); + } catch (NoSuchFieldException | IllegalAccessException ignored) { + // Just fallback and trigger the warning, this will break in Gradle 9 + return projectDependency.getDependencyProject(); + } + } } diff --git a/src/main/java/net/fabricmc/loom/util/gradle/SelfResolvingDependencyUtils.java b/src/main/java/net/fabricmc/loom/util/gradle/SelfResolvingDependencyUtils.java deleted file mode 100644 index cfbddd98..00000000 --- a/src/main/java/net/fabricmc/loom/util/gradle/SelfResolvingDependencyUtils.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2024 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.util.gradle; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Set; - -import javax.annotation.Nullable; - -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.FileCollectionDependency; -import org.gradle.api.artifacts.ProjectDependency; - -// SelfResolvingDependency is deprecated for removal, use reflection to ensure backwards compat. -@Deprecated -public class SelfResolvingDependencyUtils { - // Set this system prop to disable SRD support before Gradle does. - public static final boolean DISABLE_SRD_SUPPORT = System.getProperty("fabric.loom.disable.srd") != null; - - private static final String SELF_RESOLVING_DEPENDENCY_CLASS_NAME = "org.gradle.api.artifacts.SelfResolvingDependency"; - @Nullable - private static final Class SELF_RESOLVING_DEPENDENCY_CLASS = getSelfResolvingDependencyOrNull(); - @Nullable - private static final Method RESOLVE_METHOD = getResolveMethod(SELF_RESOLVING_DEPENDENCY_CLASS); - - /** - * @return true when dependency is a SelfResolvingDependency but NOT a FileCollectionDependency. - */ - public static boolean isExplicitSRD(Dependency dependency) { - // FileCollectionDependency is usually the replacement for SelfResolvingDependency - if (dependency instanceof FileCollectionDependency) { - return false; - } else if (dependency instanceof ProjectDependency) { - return false; - } - - return isSRD(dependency); - } - - private static boolean isSRD(Dependency dependency) { - if (SELF_RESOLVING_DEPENDENCY_CLASS == null) { - return false; - } - - return dependency.getClass().isAssignableFrom(SELF_RESOLVING_DEPENDENCY_CLASS); - } - - public static Set resolve(Dependency dependency) { - if (!isSRD(dependency)) { - throw new IllegalStateException("dependency is not a SelfResolvingDependency"); - } - - try { - return (Set) RESOLVE_METHOD.invoke(dependency); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("Failed to resolve SelfResolvingDependency", e); - } - } - - @Nullable - private static Class getSelfResolvingDependencyOrNull() { - if (DISABLE_SRD_SUPPORT) { - // Lets pretend SRD doesnt exist. - return null; - } - - try { - return Class.forName(SELF_RESOLVING_DEPENDENCY_CLASS_NAME); - } catch (ClassNotFoundException e) { - // Gradle 9+ - return null; - } - } - - @Nullable - private static Method getResolveMethod(Class clazz) { - if (clazz == null) { - // Gradle 9+ - return null; - } - - try { - var method = clazz.getDeclaredMethod("resolve"); - method.setAccessible(true); - return method; - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Failed to get SelfResolvingDependency.resolve() method", e); - } - } -} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/SplitProjectTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/SplitProjectTest.groovy index 11b5e8bb..d0b476ca 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/SplitProjectTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/SplitProjectTest.groovy @@ -43,6 +43,7 @@ class SplitProjectTest extends Specification implements GradleProjectTestTrait { then: result.task(":build").outcome == SUCCESS + result.task(":test").outcome == SUCCESS where: version << STANDARD_TEST_VERSIONS diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy index 1e0c4116..dd417fe9 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/LoomMocks.groovy @@ -24,9 +24,11 @@ package net.fabricmc.loom.test.unit +import java.nio.file.Path import java.util.function.Function import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider +import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService import net.fabricmc.loom.test.util.GradleTestUtil import net.fabricmc.loom.util.download.Download @@ -49,4 +51,20 @@ class LoomMocks { when(mock.getRefreshDeps()).thenReturn(refreshDeps) return mock } + + static IntermediateMappingsService.Options intermediateMappingsServiceOptionsMock(Path intermediaryTiny, String expectedSrcNs) { + def intermediaryTinyProperty = GradleTestUtil.mockProperty(intermediaryTiny) + def expectedSrcNsProperty = GradleTestUtil.mockProperty(expectedSrcNs) + + def mock = spy(IntermediateMappingsService.Options.class) + when(mock.getIntermediaryTiny()).thenReturn(intermediaryTinyProperty) + when(mock.getExpectedSrcNs()).thenReturn(expectedSrcNsProperty) + return mock + } + + static IntermediateMappingsService intermediateMappingsServiceMock(IntermediateMappingsService.Options options) { + def mock = spy(IntermediateMappingsService.class) + when(mock.getOptions()).thenReturn(options) + return mock + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/MappingsMergerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/MappingsMergerTest.groovy new file mode 100644 index 00000000..8bf8db99 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/MappingsMergerTest.groovy @@ -0,0 +1,291 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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 + +import java.nio.file.Files +import java.nio.file.Path + +import spock.lang.TempDir + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace +import net.fabricmc.loom.configuration.providers.mappings.IntermediateMappingsService +import net.fabricmc.loom.configuration.providers.mappings.tiny.MappingsMerger +import net.fabricmc.mappingio.MappingReader +import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch +import net.fabricmc.mappingio.tree.MemoryMappingTree + +import static org.junit.jupiter.api.Assertions.* + +class MappingsMergerTest { + @TempDir + Path tempDir + + def "mappings merger"() { + given: + Path intermediaryTiny = tempDir.resolve("intermediary.tiny") + Path mappingsTiny = tempDir.resolve("mappings.tiny") + Path mergedMappingsTiny = tempDir.resolve("merged_mappings.tiny") + + Files.writeString(intermediaryTiny, INTERMEDIARY_MAPPINGS) + Files.writeString(mappingsTiny, NAMED_MAPPINGS) + + IntermediateMappingsService.Options intermediateMappingsServiceOptions = LoomMocks.intermediateMappingsServiceOptionsMock(intermediaryTiny, OFFICIAL) + IntermediateMappingsService intermediateMappingsService = LoomMocks.intermediateMappingsServiceMock(intermediateMappingsServiceOptions) + + when: + MappingsMerger.mergeAndSaveMappings(mappingsTiny, mergedMappingsTiny, intermediateMappingsService) + + def mappings = new MemoryMappingTree() + MappingReader.read(mergedMappingsTiny, mappings) + + then: + mappings.srcNamespace == OFFICIAL + mappings.dstNamespaces == [INTERMEDIARY, NAMED] + def namedNs = mappings.getNamespaceId(NAMED) + mappings.classes.size() == 2 + mappings.classes[0].srcName == "a" + mappings.classes[0].getDstName(namedNs) == "net/fabricmc/loom/test/unit/ObfuscatedClass" + mappings.classes[0].comment == "class comment" + mappings.classes[0].fields.size() == 1 + mappings.classes[0].fields[0].srcName == "a" + mappings.classes[0].fields[0].getDstDesc(namedNs) == "obfuscatedField" + mappings.classes[0].fields[0].comment == "field comment" + mappings.classes[0].methods.size() == 1 + mappings.classes[0].methods[0].srcName == "a" + mappings.classes[0].methods[0].getDstDesc(namedNs) == "obfuscatedMethod" + mappings.classes[0].methods[0].comment == "method comment" + mappings.classes[0].methods[0].args.size() == 1 + mappings.classes[0].methods[1].args[0].getDstName(namedNs) == "obfuscatedMethodParameter" + mappings.classes[1].srcName == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + mappings.classes[1].getDstName(namedNs) == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + mappings.classes[1].comment == "class comment" + mappings.classes[1].fields.size() == 1 + mappings.classes[1].fields[0].srcName == "unobfuscatedField" + mappings.classes[1].fields[0].getDstDesc(namedNs) == "unobfuscatedField" + mappings.classes[1].fields[0].comment == "field comment" + mappings.classes[1].methods.size() == 1 + mappings.classes[1].methods[0].srcName == "unobfuscatedMethod" + mappings.classes[1].methods[0].getDstDesc(namedNs) == "unobfuscatedMethod" + mappings.classes[1].methods[0].comment == "method comment" + mappings.classes[1].methods[0].args.size() == 1 + mappings.classes[1].methods[1].args[0].getDstName(namedNs) == "unobfuscatedMethodParameter" + } + + def "mappings merger legacy"() { + given: + Path intermediaryTiny = tempDir.resolve("intermediary.tiny") + Path mappingsTiny = tempDir.resolve("mappings.tiny") + Path mergedMappingsTiny = tempDir.resolve("merged_mappings.tiny") + + Files.writeString(intermediaryTiny, LEGACY_INTERMEDIARY_MAPPINGS) + Files.writeString(mappingsTiny, LEGACY_NAMED_MAPPINGS) + + IntermediateMappingsService.Options intermediateMappingsServiceOptions = LoomMocks.intermediateMappingsServiceOptionsMock(intermediaryTiny, INTERMEDIARY) + IntermediateMappingsService intermediateMappingsService = LoomMocks.intermediateMappingsServiceMock(intermediateMappingsServiceOptions) + + when: + MappingsMerger.legacyMergeAndSaveMappings(mappingsTiny, mergedMappingsTiny, intermediateMappingsService) + + def mappings = new MemoryMappingTree() + MappingReader.read(mergedMappingsTiny, mappings) + + def clientMappings = new MemoryMappingTree() + def serverMappings = new MemoryMappingTree() + + mappings.accept(new MappingSourceNsSwitch(clientMappings, CLIENT_OFFICIAL, true)) + mappings.accept(new MappingSourceNsSwitch(serverMappings, SERVER_OFFICIAL, true)) + + then: + clientMappings.srcNamespace == CLIENT_OFFICIAL + clientMappings.dstNamespaces == [ + INTERMEDIARY, + SERVER_OFFICIAL, + NAMED + ] + def clientNamedNs = clientMappings.getNamespaceId(NAMED) + clientMappings.classes.size() == 3 + clientMappings.classes[0].srcName == "a" + clientMappings.classes[0].getDstName(namedNs) == "net/fabricmc/loom/test/unit/CommonObfuscatedClass" + clientMappings.classes[0].comment == "class comment" + clientMappings.classes[0].fields.size() == 1 + clientMappings.classes[0].fields[0].srcName == "a" + clientMappings.classes[0].fields[0].getDstDesc(namedNs) == "commonObfuscatedField" + clientMappings.classes[0].fields[0].comment == "field comment" + clientMappings.classes[0].methods.size() == 1 + clientMappings.classes[0].methods[0].srcName == "a" + clientMappings.classes[0].methods[0].getDstDesc(namedNs) == "commonObfuscatedMethod" + clientMappings.classes[0].methods[0].comment == "method comment" + clientMappings.classes[0].methods[0].args.size() == 1 + clientMappings.classes[0].methods[1].args[0].getDstName(namedNs) == "commonObfuscatedMethodParameter" + clientMappings.classes[1].srcName == "b" + clientMappings.classes[1].getDstName(namedNs) == "net/fabricmc/loom/test/unit/ClientObfuscatedClass" + clientMappings.classes[1].comment == "class comment" + clientMappings.classes[1].fields.size() == 1 + clientMappings.classes[1].fields[0].srcName == "a" + clientMappings.classes[1].fields[0].getDstDesc(namedNs) == "clientObfuscatedField" + clientMappings.classes[1].fields[0].comment == "field comment" + clientMappings.classes[1].methods.size() == 1 + clientMappings.classes[1].methods[0].srcName == "a" + clientMappings.classes[1].methods[0].getDstDesc(namedNs) == "clientObfuscatedMethod" + clientMappings.classes[1].methods[0].comment == "method comment" + clientMappings.classes[1].methods[0].args.size() == 1 + clientMappings.classes[1].methods[1].args[0].getDstName(namedNs) == "clientObfuscatedMethodParameter" + clientMappings.classes[2].srcName == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + clientMappings.classes[2].getDstName(namedNs) == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + clientMappings.classes[2].comment == "class comment" + clientMappings.classes[2].fields.size() == 1 + clientMappings.classes[2].fields[0].srcName == "unobfuscatedField" + clientMappings.classes[2].fields[0].getDstDesc(namedNs) == "unobfuscatedField" + clientMappings.classes[2].fields[0].comment == "field comment" + clientMappings.classes[2].methods.size() == 1 + clientMappings.classes[2].methods[0].srcName == "unobfuscatedMethod" + clientMappings.classes[2].methods[0].getDstDesc(namedNs) == "unobfuscatedMethod" + clientMappings.classes[2].methods[0].comment == "method comment" + clientMappings.classes[2].methods[0].args.size() == 1 + clientMappings.classes[2].methods[1].args[0].getDstName(namedNs) == "unobfuscatedMethodParameter" + + serverMappings.srcNamespace == SERVER_OFFICIAL + serverMappings.dstNamespaces == [ + INTERMEDIARY, + CLIENT_OFFICIAL, + NAMED + ] + def serverNamedNs = serverMappings.getNamespaceId(NAMED) + serverMappings.classes.size() == 3 + serverMappings.classes[0].srcName == "a" + serverMappings.classes[0].getDstName(namedNs) == "net/fabricmc/loom/test/unit/CommonObfuscatedClass" + serverMappings.classes[0].comment == "class comment" + serverMappings.classes[0].fields.size() == 1 + serverMappings.classes[0].fields[0].srcName == "a" + serverMappings.classes[0].fields[0].getDstDesc(namedNs) == "commonObfuscatedField" + serverMappings.classes[0].fields[0].comment == "field comment" + serverMappings.classes[0].methods.size() == 1 + serverMappings.classes[0].methods[0].srcName == "a" + serverMappings.classes[0].methods[0].getDstDesc(namedNs) == "commonObfuscatedMethod" + serverMappings.classes[0].methods[0].comment == "method comment" + serverMappings.classes[0].methods[0].args.size() == 1 + serverMappings.classes[0].methods[1].args[0].getDstName(namedNs) == "commonObfuscatedMethodParameter" + serverMappings.classes[1].srcName == "b" + serverMappings.classes[1].getDstName(namedNs) == "net/fabricmc/loom/test/unit/ClientObfuscatedClass" + serverMappings.classes[1].comment == "class comment" + serverMappings.classes[1].fields.size() == 1 + serverMappings.classes[1].fields[0].srcName == "a" + serverMappings.classes[1].fields[0].getDstDesc(namedNs) == "clientObfuscatedField" + serverMappings.classes[1].fields[0].comment == "field comment" + serverMappings.classes[1].methods.size() == 1 + serverMappings.classes[1].methods[0].srcName == "a" + serverMappings.classes[1].methods[0].getDstDesc(namedNs) == "clientObfuscatedMethod" + serverMappings.classes[1].methods[0].comment == "method comment" + serverMappings.classes[1].methods[0].args.size() == 1 + serverMappings.classes[1].methods[1].args[0].getDstName(namedNs) == "clientObfuscatedMethodParameter" + serverMappings.classes[2].srcName == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + serverMappings.classes[2].getDstName(namedNs) == "net/fabricmc/loom/test/unit/UnobfuscatedClass" + serverMappings.classes[2].comment == "class comment" + serverMappings.classes[2].fields.size() == 1 + serverMappings.classes[2].fields[0].srcName == "unobfuscatedField" + serverMappings.classes[2].fields[0].getDstDesc(namedNs) == "unobfuscatedField" + serverMappings.classes[2].fields[0].comment == "field comment" + serverMappings.classes[2].methods.size() == 1 + serverMappings.classes[2].methods[0].srcName == "unobfuscatedMethod" + serverMappings.classes[2].methods[0].getDstDesc(namedNs) == "unobfuscatedMethod" + serverMappings.classes[2].methods[0].comment == "method comment" + serverMappings.classes[2].methods[0].args.size() == 1 + serverMappings.classes[2].methods[1].args[0].getDstName(namedNs) == "unobfuscatedMethodParameter" + } + + private static final String OFFICIAL = MappingsNamespace.OFFICIAL.toString() + private static final String CLIENT_OFFICIAL = MappingsNamespace.CLIENT_OFFICIAL.toString() + private static final String SERVER_OFFICIAL = MappingsNamespace.SERVER_OFFICIAL.toString() + private static final String INTERMEDIARY = MappingsNamespace.INTERMEDIARY.toString() + private static final String NAMED = MappingsNamespace.NAMED.toString() + + private static final String INTERMEDIARY_MAPPINGS = """ +tiny\t2\t0\tofficial\tintermediary +c\ta\tclass_1 +\tf\tZ\ta\tfield_1 +\tm\t(Z)V\ta\tmethod_1 +""".trim() + private static final String NAMED_MAPPINGS = """ +tiny\t2\t0\tintermediary\tnamed +c\tclass_1\tnet/fabricmc/loom/test/unit/ObfuscatedClass +\tc\tclass comment +\tf\tZ\tfield_1\tobfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tmethod_1\tobfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tobfuscatedMethodParameter +c\tnet/fabricmc/loom/test/unit/UnobfuscatedClass\tnet/fabricmc/loom/test/unit/UnobfuscatedClass +\tc\tclass comment +\tf\tZ\tunobfuscatedField\tunobfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tunobfuscatedMethod\tunobfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tunobfuscatedMethodParameter +""".trim() + + private static final String LEGACY_INTERMEDIARY_MAPPINGS = """ +tiny\t2\t0\tintermediary\tclientOfficial\tserverOfficial +c\tclass_1\ta\ta +\tf\tZ\tfield_1\ta\ta +\tm\t(Z)V\tmethod_1\ta\ta +c\tclass_2\tc\t +\tf\tZ\tfield_2\ta\t +\tm\t(Z)V\tmethod_2\ta\t +c\tclass_3\t\tc +\tf\tZ\tfield_3\t\ta +\tm\t(Z)V\tmethod_3\t\ta +""".trim() + private static final String LEGACY_NAMED_MAPPINGS = """ +tiny\t2\t0\tintermediary\tnamed +c\tclass_1\tnet/fabricmc/loom/test/unit/CommonObfuscatedClass +\tc\tclass comment +\tf\tZ\tfield_1\tcommonObfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tmethod_1\tcommonObfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tcommonObfuscatedMethodParameter +c\tclass_2\tnet/fabricmc/loom/test/unit/ClientObfuscatedClass +\tc\tclass comment +\tf\tZ\tfield_2\tclientObfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tmethod_2\tclientObfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tclientObfuscatedMethodParameter +c\tclass_3\tnet/fabricmc/loom/test/unit/ServerObfuscatedClass +\tc\tclass comment +\tf\tZ\tfield_3\tserverObfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tmethod_3\tserverObfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tserverObfuscatedMethodParameter +c\tnet/fabricmc/loom/test/unit/UnobfuscatedClass\tnet/fabricmc/loom/test/unit/UnobfuscatedClass +\tc\tclass comment +\tf\tZ\tunobfuscatedField\tunobfuscatedField +\t\tc\tfield comment +\tm\t(Z)V\tunobfuscatedMethod\tunobfuscatedMethod +\t\tc\tmethod comment +\t\tp\t0\t\t\tunobfuscatedMethodParameter +""".trim() +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/processor/InterfaceInjectionProcessorTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/processor/InterfaceInjectionProcessorTest.groovy index cf66e0c8..fb0d60d3 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/processor/InterfaceInjectionProcessorTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/processor/InterfaceInjectionProcessorTest.groovy @@ -39,6 +39,8 @@ import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor import net.fabricmc.loom.test.unit.processor.classes.AdvancedGenericInterface import net.fabricmc.loom.test.unit.processor.classes.AdvancedGenericTargetClass import net.fabricmc.loom.test.unit.processor.classes.DoubleGenericTargetClass +import net.fabricmc.loom.test.unit.processor.classes.DoublePassingGenericInterface +import net.fabricmc.loom.test.unit.processor.classes.DoublePassingGenericTargetClass import net.fabricmc.loom.test.unit.processor.classes.FirstGenericInterface import net.fabricmc.loom.test.unit.processor.classes.GenericInterface import net.fabricmc.loom.test.unit.processor.classes.GenericTargetClass @@ -137,6 +139,12 @@ class InterfaceInjectionProcessorTest extends Specification { loadedClass.interfaces.first().name == "net/fabricmc/loom/test/unit/proessor/classes/SelfGenericInterface" loadedClass.constructors.first().newInstance().selfGenericInjectedMethod() == null } + + // Class using double generics and passing them to the interface + "class_9" | "net/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericInterface" | DoublePassingGenericTargetClass.class | { Class loadedClass -> + loadedClass.interfaces.first().name == "net/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericTargetClass" + loadedClass.constructors.first().newInstance().doublePassingGenericInjectedMethod().getClass() == DoublePassingGenericTargetClass.Pair.class + } } def "nothing to inject"() { @@ -230,7 +238,10 @@ class InterfaceInjectionProcessorTest extends Specification { FirstGenericInterface.class, SecondGenericInterface.class, SelfGenericTargetClass.class, - SelfGenericInterface.class + SelfGenericInterface.class, + DoublePassingGenericTargetClass.class, + DoublePassingGenericTargetClass.Pair.class, + DoublePassingGenericInterface.class ] private static final String MAPPINGS = """ @@ -243,5 +254,7 @@ c\tclass_5\tnet/fabricmc/loom/test/unit/processor/classes/AdvancedGenericTargetC c\tclass_5\$class_6\tnet/fabricmc/loom/test/unit/processor/classes/AdvancedGenericTargetClass\$Pair c\tclass_7\tnet/fabricmc/loom/test/unit/processor/classes/DoubleGenericTargetClass c\tclass_8\tnet/fabricmc/loom/test/unit/processor/classes/SelfGenericTargetClass +c\tclass_9\tnet/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericTargetClass +c\tclass_9\$class_10\tnet/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericTargetClass\$Pair """.trim() } diff --git a/src/test/groovy/net/fabricmc/loom/test/util/ExtractTestProject.groovy b/src/test/groovy/net/fabricmc/loom/test/util/ExtractTestProject.groovy new file mode 100644 index 00000000..d0d86a8b --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/util/ExtractTestProject.groovy @@ -0,0 +1,83 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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 + +// A helper script to extract the test project from the resources and add the gradle wrapper +// Useful for testing an intergration test project in a real environment +class ExtractTestProject { + static void main(String[] args) { + if (args.length != 1) { + throw new IllegalArgumentException("Expected one argument: the project name") + } + + def projectName = args[0] + def targetDirectory = new File("test/$projectName") + def sourceDirectory = new File("src/test/resources/projects/$projectName") + + if (targetDirectory.exists()) { + targetDirectory.deleteDir() + } + + copyDir(sourceDirectory, targetDirectory) + copyDir(new File("gradle"), new File(targetDirectory, "gradle")) + copyFile(new File("gradlew"), new File(targetDirectory, "gradlew")) + copyFile(new File("gradlew.bat"), new File(targetDirectory, "gradlew.bat")) + + new File(targetDirectory, "settings.gradle").text = """ + pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + mavenCentral() + gradlePluginPortal() + mavenLocal() + } + } + """ + + def buildGradle = new File(targetDirectory, "build.gradle") + buildGradle.text = buildGradle.text.replace("id 'fabric-loom'", "id 'fabric-loom' version '1.8.local'") + } + + private static void copyDir(File source, File target) { + source.eachFileRecurse { file -> + if (file.isDirectory()) { + return + } + + def relativePath = source.toPath().relativize(file.toPath()) + def targetFile = new File(target, relativePath.toString()) + copyFile(file, targetFile) + } + } + + private static void copyFile(File source, File target) { + target.parentFile.mkdirs() + target.bytes = source.bytes + println("Copied $source to $target") + } +} diff --git a/src/main/java/net/fabricmc/loom/extension/LoomProblemReporter.java b/src/test/java/net/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericInterface.java similarity index 57% rename from src/main/java/net/fabricmc/loom/extension/LoomProblemReporter.java rename to src/test/java/net/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericInterface.java index 92aa6bc6..fc120cd1 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomProblemReporter.java +++ b/src/test/java/net/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericInterface.java @@ -22,30 +22,10 @@ * SOFTWARE. */ -package net.fabricmc.loom.extension; +package net.fabricmc.loom.test.unit.processor.classes; -import javax.inject.Inject; - -import org.gradle.api.problems.ProblemReporter; -import org.gradle.api.problems.Problems; -import org.gradle.api.problems.Severity; - -public abstract class LoomProblemReporter { - private final ProblemReporter problemReporter; - - @Inject - public LoomProblemReporter(Problems problems) { - this.problemReporter = problems.forNamespace("net.fabricmc.loom"); - } - - public void reportSelfResolvingDependencyUsage() { - problemReporter.reporting(spec -> spec - .id("loom-deprecated-selfresolvingdependency", "SelfResolvingDependency is deprecated") - .details("SelfResolvingDependency has been deprecated for removal in Gradle 9") - .solution("Please replace usages of SelfResolvingDependency") - .documentedAt("https://github.com/gradle/gradle/pull/27420") - .severity(Severity.WARNING) - .stackLocation() - ); +public interface DoublePassingGenericInterface { + default DoublePassingGenericTargetClass.Pair doublePassingGenericInjectedMethod() { + return new DoublePassingGenericTargetClass.Pair<>(null, null); } } diff --git a/src/test/java/net/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericTargetClass.java b/src/test/java/net/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericTargetClass.java new file mode 100644 index 00000000..a6bc5546 --- /dev/null +++ b/src/test/java/net/fabricmc/loom/test/unit/processor/classes/DoublePassingGenericTargetClass.java @@ -0,0 +1,32 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.classes; + +public class DoublePassingGenericTargetClass { + public static class Pair { + Pair(F ignoredF, S ignoredS) { + } + } +} diff --git a/src/test/resources/projects/splitSources/build.gradle b/src/test/resources/projects/splitSources/build.gradle index a074a191..6dd9d5f7 100644 --- a/src/test/resources/projects/splitSources/build.gradle +++ b/src/test/resources/projects/splitSources/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'dev.architectury.loom' version '0.12.local' + id 'dev.architectury.loom' id 'maven-publish' } @@ -24,13 +24,18 @@ loom { dependencies { minecraft "com.mojang:minecraft:1.18.2" mappings "net.fabricmc:yarn:1.18.2+build.1:v2" - modImplementation "net.fabricmc:fabric-loader:0.13.3" + modImplementation "net.fabricmc:fabric-loader:0.16.5" + testImplementation "net.fabricmc:fabric-loader-junit:0.16.5" modImplementation "net.fabricmc.fabric-api:fabric-api:0.47.8+1.18.2" shade "com.googlecode.json-simple:json-simple:1.1.1" } +test { + useJUnitPlatform() +} + tasks.withType(JavaCompile).configureEach { it.options.release = 17 } diff --git a/src/test/resources/projects/splitSources/src/client/resources/modid.client.mixins.json b/src/test/resources/projects/splitSources/src/client/resources/modid.client.mixins.json index 7c42cb48..4554aee8 100644 --- a/src/test/resources/projects/splitSources/src/client/resources/modid.client.mixins.json +++ b/src/test/resources/projects/splitSources/src/client/resources/modid.client.mixins.json @@ -1,7 +1,7 @@ { "required": true, "minVersion": "0.8", - "package": "net.fabricmc.example.mixin", + "package": "net.fabricmc.example.client.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ ], diff --git a/src/test/resources/projects/splitSources/src/test/java/net/fabricmc/example/test/ExampleModTest.java b/src/test/resources/projects/splitSources/src/test/java/net/fabricmc/example/test/ExampleModTest.java new file mode 100644 index 00000000..41fa2069 --- /dev/null +++ b/src/test/resources/projects/splitSources/src/test/java/net/fabricmc/example/test/ExampleModTest.java @@ -0,0 +1,17 @@ +package net.fabricmc.example.test; + +import net.fabricmc.example.client.ExampleModClient; + +import net.minecraft.client.MinecraftClient; +import org.junit.jupiter.api.Test; + +public class ExampleModTest { + @Test + void testClientClass() { + // Check we can compile against our own client code + ExampleModClient exampleModClient = new ExampleModClient(); + + // And minecrafts client code + MinecraftClient client = null; + } +}