diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml
index a4971308..560de2a7 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 c870148e..bfb2f8b8 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.nio.file.Path;
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 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
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 f3a93922..7e83e24c 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 final class IntermediaryNamespaces {
/**
* 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 37053c9a..6b527d0c 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 @@ public abstract class CompileConfiguration implements Runnable {
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 1c81d8ad..f954ab3c 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 record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
} 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 ff350dfe..b7d78334 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 class ModConfigurationRemapper {
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 b5fab0bc..952a5022 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 @@ public class ModProcessor {
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 7243f27d..66d5b093 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 class ForgeLibrariesProvider {
}
}
+ 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 b62a9d1c..1be328d0 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 org.gradle.api.Project;
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 class ForgeProvider extends DependencyProvider {
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 3f56b844..624a2630 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 record ForgeRunTemplate(
// 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 0fe3953c..3cc9b6bb 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 dev.architectury.loom.forge.UserdevConfig;
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.api.ModSettings;
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 @@ public class ForgeRunsProvider {
// 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 @@ public class ForgeRunsProvider {
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 @@ public class ForgeRunsProvider {
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 2e52b85b..e9b4b9e8 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.Path;
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 @@ public class MappingConfiguration {
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 class MappingConfiguration {
}
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 class MappingConfiguration {
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 class MappingConfiguration {
}
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 fd432fb6..9b2656b0 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 @@ public abstract class AbstractMappedMinecraftProvider !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 14530dda..237678f3 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 class Constants {
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 ed4d718f..18699011 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 class SourceRemapper {
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 @@ public class SourceRemapper {
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 00000000..dded1682
--- /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 f19cd9cc..5c9f1e81 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 00000000..a182dd55
--- /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 36bc2f7b..e398cfbf 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 00000000..d4cda6cd
--- /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 00000000..d55eec7a
--- /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