diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml index a49713084..560de2a79 100644 --- a/gradle/runtime.libs.versions.toml +++ b/gradle/runtime.libs.versions.toml @@ -19,7 +19,6 @@ access-transformers-new = "8.0.5" access-transformers-neo = "10.0.2" unprotect = "1.2.0" asm = "9.7" -union-relauncher = "1.1.1" access-transformers-log4j = "2.17.1" [libraries] @@ -45,5 +44,4 @@ access-transformers-new = { module = "net.minecraftforge:accesstransformers", ve access-transformers-neo = { module = "net.neoforged.accesstransformers:at-cli", version.ref = "access-transformers-neo" } unprotect = { module = "io.github.juuxel:unprotect", version.ref = "unprotect" } asm = { module = "org.ow2.asm:asm", version.ref = "asm" } -union-relauncher = { module = "io.github.juuxel:union-relauncher", version.ref = "union-relauncher" } access-transformers-log4j-bom = { module = "org.apache.logging.log4j:log4j-bom", version.ref = "access-transformers-log4j" } diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index c870148e5..bfb2f8b83 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.List; +import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; @@ -114,7 +115,10 @@ default List getMinecraftJars(MappingsNamespace mappingsNamespace) { yield getSrgMinecraftProvider().getMinecraftJarPaths(); } case MOJANG -> { - ModPlatform.assertPlatform(this, ModPlatform.NEOFORGE, () -> "Mojang-mapped jars are only available on NeoForge."); + if (!this.isForgeLike() || !this.getForgeProvider().usesMojangAtRuntime()) { + throw new GradleException("Mojang-mapped jars are only available on NeoForge / Forge 50+."); + } + yield getMojangMappedMinecraftProvider().getMinecraftJarPaths(); } }; diff --git a/src/main/java/net/fabricmc/loom/build/IntermediaryNamespaces.java b/src/main/java/net/fabricmc/loom/build/IntermediaryNamespaces.java index f3a93922e..7e83e24c0 100644 --- a/src/main/java/net/fabricmc/loom/build/IntermediaryNamespaces.java +++ b/src/main/java/net/fabricmc/loom/build/IntermediaryNamespaces.java @@ -76,7 +76,7 @@ public static MappingsNamespace runtimeIntermediaryNamespace(Project project) { /** * Potentially replaces the remapping target namespace for mixin refmaps. * - *

All {@linkplain #intermediary(Project) intermediary-like namespaces} are replaced + *

All {@linkplain #runtimeIntermediary(Project) intermediary-like namespaces} are replaced * by {@code intermediary} since fabric-mixin-compile-extensions only supports intermediary. * We transform the namespaces in the input mappings, e.g. {@code intermediary} -> {@code yraidemretni} and * {@code srg} -> {@code intermediary}. diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 37053c9a8..6b527d0c1 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -268,7 +268,9 @@ private synchronized void setupMinecraft(ConfigContext configContext) throws Exc final SrgMinecraftProvider srgMinecraftProvider = jarConfiguration.createSrgMinecraftProvider(project); extension.setSrgMinecraftProvider(srgMinecraftProvider); srgMinecraftProvider.provide(provideContext); - } else if (extension.isNeoForge()) { + } + + if (extension.isForgeLike() && extension.getForgeProvider().usesMojangAtRuntime()) { final MojangMappedMinecraftProvider mojangMappedMinecraftProvider = jarConfiguration.createMojangMappedMinecraftProvider(project); extension.setMojangMappedMinecraftProvider(mojangMappedMinecraftProvider); mojangMappedMinecraftProvider.provide(provideContext); diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java b/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java index 1c81d8ad0..f954ab3c0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactMetadata.java @@ -55,10 +55,10 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi private static final String QUILT_INSTALLER_PATH = "quilt_installer.json"; public static ArtifactMetadata create(ArtifactRef artifact, String currentLoomVersion) throws IOException { - return create(artifact, currentLoomVersion, ModPlatform.FABRIC); + return create(artifact, currentLoomVersion, ModPlatform.FABRIC, null); } - public static ArtifactMetadata create(ArtifactRef artifact, String currentLoomVersion, ModPlatform platform) throws IOException { + public static ArtifactMetadata create(ArtifactRef artifact, String currentLoomVersion, ModPlatform platform, @Nullable Boolean forcesStaticMixinRemap) throws IOException { boolean isFabricMod; RemapRequirements remapRequirements = RemapRequirements.DEFAULT; InstallerData installerData = null; @@ -93,11 +93,10 @@ public static ArtifactMetadata create(ArtifactRef artifact, String currentLoomVe } catch (IllegalArgumentException e) { throw new IllegalStateException("Unknown mixin remap type: " + mixinRemapType); } - } else if (platform == ModPlatform.FORGE) { - // Use certain refmap remap types by the current platform - refmapRemapType = MixinRemapType.MIXIN; - } else if (platform == ModPlatform.NEOFORGE) { - refmapRemapType = MixinRemapType.STATIC; + } else if (forcesStaticMixinRemap != null) { + // The mixin remap type is not specified in the manifest, but we have a forced value + // This is forced to be static on NeoForge or Forge 50+. + refmapRemapType = forcesStaticMixinRemap ? MixinRemapType.STATIC : MixinRemapType.MIXIN; } if (loomVersion != null && refmapRemapType != MixinRemapType.STATIC) { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java index ff350dfe7..b7d78334c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java @@ -151,7 +151,8 @@ public static void supplyModConfigurations(Project project, SharedServiceManager artifactMetadata = metaCache.computeIfAbsent(artifact, a -> { try { - return ArtifactMetadata.create(a, LoomGradlePlugin.LOOM_VERSION, extension.getPlatform().get()); + return ArtifactMetadata.create(a, LoomGradlePlugin.LOOM_VERSION, extension.getPlatform().get(), + extension.isForgeLike() && extension.getForgeProvider().usesMojangAtRuntime() ? true : null); } catch (IOException e) { throw ExceptionUtil.createDescriptiveWrapper(UncheckedIOException::new, "Failed to read metadata from " + a.path(), e); } diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index b5fab0bcc..952a5022a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -206,7 +206,7 @@ private void remapJars(List remapList) throws IOException { final TinyRemapper remapper = builder.build(); - remapper.readClassPath(extension.getMinecraftJars(IntermediaryNamespaces.intermediaryNamespace(project)).toArray(Path[]::new)); + remapper.readClassPath(extension.getMinecraftJars(IntermediaryNamespaces.runtimeIntermediaryNamespace(project)).toArray(Path[]::new)); final Map tagMap = new HashMap<>(); final Map outputConsumerMap = new HashMap<>(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeLibrariesProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeLibrariesProvider.java index 7243f27d1..66d5b0932 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeLibrariesProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeLibrariesProvider.java @@ -96,6 +96,13 @@ public static void provide(MappingConfiguration mappingConfiguration, Project pr } } + if (lib.startsWith("net.minecraftforge:bootstrap:")) { + if (extension.isForge() && extension.getForgeProvider().getVersion().getMajorVersion() >= Constants.Forge.MIN_BOOTSTRAP_DEV_VERSION) { + String version = lib.substring(lib.lastIndexOf(":")); + dependencies.add(project.getDependencies().create("net.minecraftforge:bootstrap-dev" + version)); + } + } + if (dep == null) { dep = lib; } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java index b62a9d1cc..1be328d04 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java @@ -32,7 +32,6 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.DependencyInfo; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.LoomVersions; import net.fabricmc.loom.util.ModPlatform; public class ForgeProvider extends DependencyProvider { @@ -50,10 +49,6 @@ public void provide(DependencyInfo dependency) throws Exception { version = new ForgeVersion(dependency.getResolvedVersion()); addDependency(dependency.getDepString() + ":userdev", Constants.Configurations.FORGE_USERDEV); addDependency(dependency.getDepString() + ":installer", Constants.Configurations.FORGE_INSTALLER); - - if (getExtension().isForge() && version.getMajorVersion() >= Constants.Forge.MIN_UNION_RELAUNCHER_VERSION) { - addDependency(LoomVersions.UNION_RELAUNCHER.mavenNotation(), Constants.Configurations.FORGE_EXTRA); - } } public ForgeVersion getVersion() { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeRunTemplate.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeRunTemplate.java index 3f56b844f..624a2630a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeRunTemplate.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeRunTemplate.java @@ -112,13 +112,6 @@ public void applyTo(RunConfigSettings settings, ConfigValue.Resolver configValue // Add MOD_CLASSES, this is something that ForgeGradle does settings.getEnvironmentVariables().computeIfAbsent("MOD_CLASSES", $ -> ConfigValue.of("{source_roots}").resolve(configValueResolver)); - - final ForgeProvider forgeProvider = settings.getExtension().getForgeProvider(); - - if (settings.getExtension().isForge() && forgeProvider.getVersion().getMajorVersion() >= Constants.Forge.MIN_UNION_RELAUNCHER_VERSION) { - settings.defaultMainClass(Constants.Forge.UNION_RELAUNCHER_MAIN_CLASS); - settings.property(Constants.Forge.UNION_RELAUNCHER_MAIN_CLASS_PROPERTY, main); - } } public Resolved resolve(ConfigValue.Resolver configValueResolver) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeRunsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeRunsProvider.java index 0fe3953ce..3cc9b6bb3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeRunsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeRunsProvider.java @@ -42,6 +42,7 @@ import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.NamedDomainObjectSet; import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; @@ -49,6 +50,7 @@ import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DependencyDownloader; +import net.fabricmc.loom.util.Version; import net.fabricmc.loom.util.gradle.SourceSetHelper; import net.fabricmc.loom.util.gradle.SourceSetReference; @@ -128,6 +130,7 @@ private String resolve(@Nullable RunConfigSettings runConfig, ConfigValue.Variab // Use a set-valued multimap for deduplicating paths. Multimap modClasses = MultimapBuilder.hashKeys().linkedHashSetValues().build(); NamedDomainObjectContainer mods = extension.getMods(); + String separator = getSourceRootsSeparator(); if (runConfig != null && !runConfig.getMods().isEmpty()) { mods = runConfig.getMods(); @@ -147,7 +150,7 @@ private String resolve(@Nullable RunConfigSettings runConfig, ConfigValue.Variab string = modClasses.entries().stream() .map(entry -> entry.getKey() + "%%" + entry.getValue()) - .collect(Collectors.joining(File.pathSeparator)); + .collect(Collectors.joining(separator)); } else if (key.equals("mcp_mappings")) { string = "loom.stub"; } else if (json.has(key)) { @@ -184,4 +187,21 @@ private Set runtimeClasspath() { private Set minecraftClasspath() { return DependencyDownloader.resolveFiles(project, project.getConfigurations().getByName(Constants.Configurations.FORGE_RUNTIME_LIBRARY), true); } + + private String getSourceRootsSeparator() { + // Some versions of Forge 49+ requires a different separator + if (!extension.isForge() || extension.getForgeProvider().getVersion().getMajorVersion() < Constants.Forge.MIN_BOOTSTRAP_DEV_VERSION) { + return File.pathSeparator; + } + + for (Dependency dependency : project.getConfigurations().getByName(Constants.Configurations.FORGE_DEPENDENCIES).getDependencies()) { + if (dependency.getGroup().equals("net.minecraftforge") && dependency.getName().equals("bootstrap-dev")) { + Version version = Version.parse(dependency.getVersion()); + return version.compareTo(Version.parse("2.1.4")) >= 0 ? File.pathSeparator : ";"; + } + } + + project.getLogger().warn("Failed to find bootstrap-dev in forge dependencies, using File.pathSeparator as separator"); + return File.pathSeparator; + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingConfiguration.java index 2e52b85ba..e9b4b9e8b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingConfiguration.java @@ -37,12 +37,14 @@ import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import com.google.common.base.Stopwatch; +import com.google.common.base.Supplier; import com.google.gson.JsonObject; import dev.architectury.loom.util.MappingOption; import org.apache.tools.ant.util.StringUtils; @@ -94,6 +96,7 @@ public class MappingConfiguration { public Path tinyMappingsWithSrg; public final Map mixinTinyMappings; // The mixin mappings have other names in intermediary. public final Path srgToNamedSrg; // FORGE: srg to named in srg file format + private final Map> mappingOptions; private final Path unpickDefinitions; private boolean hasUnpickDefinitions; @@ -112,6 +115,8 @@ protected MappingConfiguration(String mappingsIdentifier, Path mappingsWorkingDi this.tinyMappingsWithMojang = mappingsWorkingDir.resolve("mappings-mojang.tiny"); this.mixinTinyMappings = new HashMap<>(); this.srgToNamedSrg = mappingsWorkingDir.resolve("mappings-srg-named.srg"); + this.mappingOptions = new EnumMap<>(MappingOption.class); + this.mappingOptions.put(MappingOption.DEFAULT, () -> this.tinyMappings); } public static MappingConfiguration create(Project project, SharedServiceManager serviceManager, DependencyInfo dependency, MinecraftProvider minecraftProvider) { @@ -164,25 +169,15 @@ public TinyMappingsService getMappingsService(SharedServiceManager serviceManage } public TinyMappingsService getMappingsService(SharedServiceManager serviceManager, MappingOption mappingOption) { - final Path tinyMappings = switch (mappingOption) { - case WITH_SRG -> { - if (Files.notExists(this.tinyMappingsWithSrg)) { - throw new UnsupportedOperationException("Cannot get mappings service with SRG mappings without SRG enabled!"); - } + Supplier mappingsSupplier = this.mappingOptions.get(mappingOption); - yield this.tinyMappingsWithSrg; + if (mappingsSupplier == null) { + throw new UnsupportedOperationException("Unsupported mapping option: " + mappingOption + ", it is possible that this option is not supported by this project / platform!"); + } else if (Files.notExists(mappingsSupplier.get())) { + throw new UnsupportedOperationException("Mapping option " + mappingOption + " found but file does not exist!"); } - case WITH_MOJANG -> { - if (Files.notExists(this.tinyMappingsWithMojang)) { - throw new UnsupportedOperationException("Cannot get mappings service with Mojang mappings without Mojang merging enabled!"); - } - - yield this.tinyMappingsWithMojang; - } - default -> this.tinyMappings; - }; - return TinyMappingsService.create(serviceManager, Objects.requireNonNull(tinyMappings)); + return TinyMappingsService.create(serviceManager, Objects.requireNonNull(mappingsSupplier.get())); } protected void setup(Project project, SharedServiceManager serviceManager, MinecraftProvider minecraftProvider, Path inputJar) throws IOException { @@ -208,6 +203,8 @@ public void setupPost(Project project) throws IOException { LoomGradleExtension extension = LoomGradleExtension.get(project); if (extension.isNeoForge()) { + this.mappingOptions.put(MappingOption.WITH_MOJANG, () -> this.tinyMappingsWithMojang); + // Generate the Mojmap-merged mappings if needed. // Note that this needs to happen before manipulateMappings for FieldMigratedMappingConfiguration. if (Files.notExists(tinyMappingsWithMojang) || extension.refreshDeps()) { @@ -216,6 +213,12 @@ public void setupPost(Project project) throws IOException { } if (extension.shouldGenerateSrgTiny()) { + this.mappingOptions.put(MappingOption.WITH_SRG, () -> this.tinyMappingsWithSrg); + + if (extension.isForge() && extension.getForgeProvider().usesMojangAtRuntime()) { + this.mappingOptions.put(MappingOption.WITH_MOJANG, () -> this.tinyMappingsWithSrg); + } + if (Files.notExists(tinyMappingsWithSrg) || extension.refreshDeps()) { if (extension.isForge() && extension.getForgeProvider().usesMojangAtRuntime()) { Path tmp = Files.createTempFile("mappings", ".tiny"); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java index fd432fb6b..9b2656b01 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java @@ -245,7 +245,7 @@ private void remapJar(RemappedJars remappedJars, ConfigContext configContext) th className = "net.minecraftforge.registries.ObjectHolderRegistry"; } - final String sourceNamespace = IntermediaryNamespaces.intermediary(project); + final String sourceNamespace = IntermediaryNamespaces.runtimeIntermediary(project); final MemoryMappingTree mappings = mappingsService.getMappingTree(); RemapObjectHolderVisitor.remapObjectHolder(remappedJars.outputJar().getPath(), className, mappings, sourceNamespace, "named"); } diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java index b511a41d5..bdcf7e657 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java @@ -50,7 +50,7 @@ public abstract class MixinExtensionApiImpl implements MixinExtensionAPI { public MixinExtensionApiImpl(Project project) { this.project = Objects.requireNonNull(project); this.useMixinAp = project.getObjects().property(Boolean.class) - .convention(project.provider(() -> !LoomGradleExtension.get(project).isNeoForge())); + .convention(project.provider(() -> !LoomGradleExtension.get(project).isNeoForge() && (!LoomGradleExtension.get(project).isForge() || !LoomGradleExtension.get(project).getForgeProvider().usesMojangAtRuntime()))); this.refmapTargetNamespace = project.getObjects().property(String.class) .convention(project.provider(() -> IntermediaryNamespaces.runtimeIntermediary(project))); diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index 14530ddac..237678f3b 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -192,11 +192,9 @@ public static final class Forge { public static final String MIXIN_CONFIGS_MANIFEST_KEY = "MixinConfigs"; /** - * The minimum Forge version that needs Union Relauncher to use {@code MOD_CLASSES}. + * The minimum Forge version that needs bootstrap-dev to use {@code MOD_CLASSES}. */ - public static final int MIN_UNION_RELAUNCHER_VERSION = 49; - public static final String UNION_RELAUNCHER_MAIN_CLASS = "juuxel.unionrelauncher.UnionRelauncher"; - public static final String UNION_RELAUNCHER_MAIN_CLASS_PROPERTY = "unionRelauncher.mainClass"; + public static final int MIN_BOOTSTRAP_DEV_VERSION = 49; /** * The minimum version of Forge that uses "mojang" as the namespace in production. diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index ed4d718f5..186990118 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -98,7 +98,7 @@ public void remapAll() { return; } - project.getLogger().lifecycle(":remapping sources"); + project.getLogger().lifecycle(":remapping sources (Mercury, {} -> {})", from, to); ProgressLoggerFactory progressLoggerFactory = ((ProjectInternal) project).getServices().get(ProgressLoggerFactory.class); ProgressLogger progressLogger = progressLoggerFactory.newOperation(SourceRemapper.class.getName()); @@ -199,13 +199,9 @@ private Mercury getMercuryInstance() { mercury.getClassPath().add(intermediaryJar); } - if (extension.isForge()) { - for (Path srgJar : extension.getMinecraftJars(MappingsNamespace.SRG)) { - mercury.getClassPath().add(srgJar); - } - } else if (extension.isNeoForge()) { - for (Path mojangJar : extension.getMinecraftJars(MappingsNamespace.MOJANG)) { - mercury.getClassPath().add(mojangJar); + if (extension.isForgeLike()) { + for (Path jar : extension.getMinecraftJars(IntermediaryNamespaces.runtimeIntermediaryNamespace(project))) { + mercury.getClassPath().add(jar); } } diff --git a/src/main/java/net/fabricmc/loom/util/Version.java b/src/main/java/net/fabricmc/loom/util/Version.java new file mode 100644 index 000000000..dded1682c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/Version.java @@ -0,0 +1,88 @@ +/* + * 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; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.base.Strings; +import com.google.common.collect.Ordering; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A simple version class that can be used to compare versions. + * This class allows for versions that are not strictly following the semver specification, + * but are still allowed in the context of gradle versioning. + * + *

This class is intentionally very flexible and does not enforce any specific versioning scheme, + * and should be very similar to the versioning used by gradle itself. + */ +public record Version(int major, int minor, int micro, int patch, @Nullable String qualifier) implements Comparable { + private static final Pattern REGEX = Pattern.compile("(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?()(?:[.-]([^+\\s]*))?(?:\\+.*)?"); + private static final Pattern REGEX_WITH_PATCH = Pattern.compile("(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:\\.(\\d+))?(?:[+.-]([^+\\s]*))?(?:\\+.*)?"); + public static final Version UNKNOWN = new Version(0, 0, 0, 0, null); + + public static Version parse(String version) { + return parse(version, false); + } + + public static Version parse(String version, boolean withPatch) { + Matcher matcher = (withPatch ? REGEX_WITH_PATCH : REGEX).matcher(version); + + if (!matcher.matches()) { + return UNKNOWN; + } + + int major = Integer.parseInt(matcher.group(1)); + int minor = !Strings.isNullOrEmpty(matcher.group(2)) ? Integer.parseInt(matcher.group(2)) : 0; + int micro = !Strings.isNullOrEmpty(matcher.group(3)) ? Integer.parseInt(matcher.group(3)) : 0; + int patch = !Strings.isNullOrEmpty(matcher.group(4)) ? Integer.parseInt(matcher.group(4)) : 0; + String qualifier = matcher.group(5); + + return new Version(major, minor, micro, patch, qualifier == null ? null : qualifier.toLowerCase(Locale.ROOT)); + } + + public Version asBaseVersion() { + return new Version(this.major, this.minor, this.micro, this.patch, null); + } + + @Override + public int compareTo(@NotNull Version other) { + if (this.major != other.major) { + return this.major - other.major; + } else if (this.minor != other.minor) { + return this.minor - other.minor; + } else if (this.micro != other.micro) { + return this.micro - other.micro; + } else if (this.patch != other.patch) { + return this.patch - other.patch; + } else { + return Ordering.natural().nullsLast() + .compare(this.qualifier, other.qualifier); + } + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/forge/Forge1206Test.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/forge/Forge1206Test.groovy index f19cd9cc8..5c9f1e81d 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/forge/Forge1206Test.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/forge/Forge1206Test.groovy @@ -34,7 +34,7 @@ import static org.gradle.testkit.runner.TaskOutcome.SUCCESS class Forge1206Test extends Specification implements GradleProjectTestTrait { @Unroll - def "build #mcVersion #neoforgeVersion #mappings #patches"() { + def "build #mcVersion #forgeVersion #mappings #patches"() { if (Integer.valueOf(System.getProperty("java.version").split("\\.")[0]) < 21) { println("This test requires Java 21. Currently you have Java ${System.getProperty("java.version")}.") return @@ -43,7 +43,7 @@ class Forge1206Test extends Specification implements GradleProjectTestTrait { setup: def gradle = gradleProject(project: "forge/1206", version: DEFAULT_GRADLE) gradle.buildGradle.text = gradle.buildGradle.text.replace('@MCVERSION@', mcVersion) - .replace('@FORGEVERSION@', neoforgeVersion) + .replace('@FORGEVERSION@', forgeVersion) .replace('MAPPINGS', mappings) // Spotless doesn't like the @'s .replace('PATCHES', patches) @@ -54,7 +54,7 @@ class Forge1206Test extends Specification implements GradleProjectTestTrait { result.task(":build").outcome == SUCCESS where: - mcVersion | neoforgeVersion | mappings | patches + mcVersion | forgeVersion | mappings | patches '1.20.6' | '1.20.6-50.1.3' | 'loom.officialMojangMappings()' | '' } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/VersionTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/VersionTest.groovy new file mode 100644 index 000000000..a182dd559 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/VersionTest.groovy @@ -0,0 +1,56 @@ +/* + * 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 spock.lang.Specification + +import net.fabricmc.loom.util.Version + +class VersionTest extends Specification { + def "version comparison"() { + when: + def compare = Version.parse(s1) <=> Version.parse(s2) + // turns compare into 1 or 0 or -1 + compare = compare <=> 0 + + then: + compare == expected + + where: + s1 | s2 | expected + "1.1.1" | "1.1.1" | 0 + "1.1.1" | "1.1.0" | 1 + "1.1.0" | "1.1" | 0 + "1.0.0" | "1" | 0 + "1-" | "1" | -1 + "1.1.1" | "1.1.2" | -1 + "1.1.1" | "1.1.1-" | 1 + "1.1.1-beta" | "1.1.1-alpha" | 1 + "1.1.1-alpha" | "1.1.1-beta" | -1 + "1.1.1-beta.1" | "1.1.1-beta.2" | -1 + "1.1.1-beta.1" | "1.1.1-beta.10" | -1 + "1.1.1+123" | "1.1.1+567" | 0 + } +} diff --git a/src/test/resources/projects/forge/1206/build.gradle b/src/test/resources/projects/forge/1206/build.gradle index 36bc2f7ba..e398cfbf6 100644 --- a/src/test/resources/projects/forge/1206/build.gradle +++ b/src/test/resources/projects/forge/1206/build.gradle @@ -18,6 +18,17 @@ group = project.maven_group def mcVersion = "@MCVERSION@" def forgeVersion = "@FORGEVERSION@" + +loom { + forge { + mixinConfig "examplemod.mixins.json" + } + + afterEvaluate { + assert mixin.useLegacyMixinAp.get() == false + } +} + repositories { // Add repositories to retrieve artifacts from in here. // You should only use this when depending on other mods because diff --git a/src/test/resources/projects/forge/1206/src/main/java/com/example/examplemod/mixin/ExampleModMixin.java b/src/test/resources/projects/forge/1206/src/main/java/com/example/examplemod/mixin/ExampleModMixin.java new file mode 100644 index 000000000..d4cda6cd2 --- /dev/null +++ b/src/test/resources/projects/forge/1206/src/main/java/com/example/examplemod/mixin/ExampleModMixin.java @@ -0,0 +1,15 @@ +package com.example.examplemod.mixin; + +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class ExampleModMixin { + @Inject(method = "", at = @At("HEAD")) + private static void initInject(CallbackInfo info) { + System.out.println("Hello from the example mod mixin!"); + } +} diff --git a/src/test/resources/projects/forge/1206/src/main/resources/examplemod.mixins.json b/src/test/resources/projects/forge/1206/src/main/resources/examplemod.mixins.json new file mode 100644 index 000000000..d55eec7ac --- /dev/null +++ b/src/test/resources/projects/forge/1206/src/main/resources/examplemod.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.example.examplemod.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + "ExampleModMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file