NeoForge support (#166)

* Initial plumbing for NeoForge support

* Fix checkstyle

* Add ModPlatform.id

* Use NeoForge-specific cache

* Use NeoForge-specific dependency configuration

This is only for the "(neo)forge" configuration exposed
as API. The other configurations remain the same.

* Add test for basic NeoForge 1.20.2 projects

* Implement hacky fast track for NeoForge field migration

In other works, we skip field migrating for now.

* Disable patched decompilation task on Neo

* Disable mixin AP for building on NeoForge

* Many changes related to NeoForge mappings and remapping

* Code style and related fixes

* McpExecutor: Add support for downloading deps via Gradle

Also adds support for downloading a file without a repo
for NeoForm functions.

* Fix wrong configurations being used on NeoForge

* Fix mixin version detection on NeoForge

* Rename MinecraftPatchedProvider jar paths on NeoForge

* Test NeoForge against a client-only MC jar

* Add DFU for codecs, support NeoForge run config templates

* Centralise userdev config reading, support missing SAS

* Set up Shadow for bundling DFU

* Use correct name for NeoForm in cache files

* RemapJarTask: Fix check using isForgeLike for Forge

* MojangMappingsMerger: Complete and reorder mappings

* Fix SRG being used on NeoForge

* Fix SRG being used on NeoForge for ATs

* Use client pipeline for merged to avoid patch issues on Neo

* Update to architectury-loom-runtime 2.0

* Fix Minecraft jar name on Neo

* Fix MojangMappingsMerger having incomplete names

* Fix NeoForge mod dependency remapping using wrong mappings

* Quiet down MojangMappingsMerger

* Fix (Neo)Forge builtin coremods not being remapped

Fixes #146.

* Disable deprecated data generation API on NeoForge

* Use release version of the forge runtime

* Revert "Set up Shadow for bundling DFU"

This reverts commit 2bb8166744.

* Make NeoForge Field Migration work

* NeoForge shouldn't try to get datagen mods

* Fix checkstyle

* Remove mojang maven

* Split Forge and NeoForge extensions

* SimpleNeoForgeTest: Bump Neo version and fix Yarn version

* Remove resolved TODOs

* Re-enable joined NeoForm pipeline

* MPP: Rename srg -> intermediate jars

* Reintroduce namespace filtering for mapping trees

Should be a simple optimisation to avoid reading an
additional ns.

* ForgeRunTemplateTest: Fix code format

* Adapt SrgMerger into ForgeMappingsMerger (#169)

* Fix crash with NeoForge ext creation

* Adapt SrgMerger into ForgeMappingsMerger

* Update tiny-remapper

* Fix spotless

* Resolve reviews

* Fix checkstyle

* Remap ASMAPI.redirectFieldToMethod (#171)

* Remap ASMAPI.redirectFieldToMethod

* Move lastClassName outside the if

* Fix missing template variables in tests using forge/simple

* Add Java version to forge/simple test variables

* Disable naming service dependency on Neo

* Fix changing patch version not affecting mapped game jars

Fixes #167.

* Rename configuration: neoforge -> neoForge

---------

Co-authored-by: shedaniel <daniel@shedaniel.me>
This commit is contained in:
Juuz
2023-11-17 16:04:22 +02:00
committed by GitHub
parent e3b51e9e97
commit a11b828380
78 changed files with 1387 additions and 469 deletions

View File

@@ -0,0 +1,45 @@
package dev.architectury.loom.forge;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.fabricmc.loom.configuration.providers.forge.ForgeRunTemplate;
public record UserdevConfig(
String mcp,
String universal,
String sources,
String patches,
Optional<String> patchesOriginalPrefix,
Optional<String> patchesModifiedPrefix,
String binpatches,
BinaryPatcherConfig binpatcher,
List<String> libraries,
Map<String, ForgeRunTemplate> runs,
List<String> sass
) {
public static final Codec<UserdevConfig> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.fieldOf("mcp").forGetter(UserdevConfig::mcp),
Codec.STRING.fieldOf("universal").forGetter(UserdevConfig::universal),
Codec.STRING.fieldOf("sources").forGetter(UserdevConfig::sources),
Codec.STRING.fieldOf("patches").forGetter(UserdevConfig::patches),
Codec.STRING.optionalFieldOf("patchesOriginalPrefix").forGetter(UserdevConfig::patchesOriginalPrefix),
Codec.STRING.optionalFieldOf("patchesModifiedPrefix").forGetter(UserdevConfig::patchesModifiedPrefix),
Codec.STRING.fieldOf("binpatches").forGetter(UserdevConfig::binpatches),
BinaryPatcherConfig.CODEC.fieldOf("binpatcher").forGetter(UserdevConfig::binpatcher),
Codec.STRING.listOf().fieldOf("libraries").forGetter(UserdevConfig::libraries),
ForgeRunTemplate.MAP_CODEC.fieldOf("runs").forGetter(UserdevConfig::runs),
Codec.STRING.listOf().optionalFieldOf("sass", List.of()).forGetter(UserdevConfig::sass)
).apply(instance, UserdevConfig::new));
public record BinaryPatcherConfig(String dependency, List<String> args) {
public static final Codec<BinaryPatcherConfig> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.fieldOf("version").forGetter(BinaryPatcherConfig::dependency),
Codec.STRING.listOf().fieldOf("args").forGetter(BinaryPatcherConfig::args)
).apply(instance, BinaryPatcherConfig::new));
}
}

View File

@@ -8,8 +8,6 @@ import java.nio.file.Path;
import java.util.List;
import java.util.StringJoiner;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable;
@@ -31,18 +29,16 @@ public final class ForgeLoggerConfig {
throw new UncheckedIOException(e);
}
final JsonArray libraries = LoomGradleExtension.get(project)
final List<String> libraries = LoomGradleExtension.get(project)
.getForgeUserdevProvider()
.getJson()
.getAsJsonArray("libraries");
.getConfig()
.libraries();
boolean found = false;
for (JsonElement library : libraries) {
final String notation = library.getAsString();
if (LOGGER_CONFIG_ARTIFACTS.stream().anyMatch(artifact -> artifact.matches(notation))) {
for (String library : libraries) {
if (LOGGER_CONFIG_ARTIFACTS.stream().anyMatch(artifact -> artifact.matches(library))) {
final File libraryFile = project.getConfigurations()
.detachedConfiguration(project.getDependencies().create(notation))
.detachedConfiguration(project.getDependencies().create(library))
.setTransitive(false)
.getSingleFile();

View File

@@ -0,0 +1,38 @@
package dev.architectury.loom.util;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
public enum MappingOption {
DEFAULT(null),
WITH_SRG(MappingsNamespace.SRG.toString()),
WITH_MOJANG(MappingsNamespace.MOJANG.toString());
private final String extraNamespace;
MappingOption(@Nullable String extraNamespace) {
this.extraNamespace = extraNamespace;
}
public MappingOption forNamespaces(String... namespaces) {
if (extraNamespace == null) return this;
for (String namespace : namespaces) {
if (extraNamespace.equals(namespace)) {
return this;
}
}
return DEFAULT;
}
public static MappingOption forPlatform(LoomGradleExtensionAPI extension) {
return switch (extension.getPlatform().get()) {
case FORGE -> WITH_SRG;
case NEOFORGE -> WITH_MOJANG;
default -> DEFAULT;
};
}
}

View File

@@ -50,6 +50,7 @@ import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessorManager;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MojangMappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
import net.fabricmc.loom.extension.LoomFiles;
@@ -95,6 +96,10 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
void setSrgMinecraftProvider(SrgMinecraftProvider<?> srgMinecraftProvider);
MojangMappedMinecraftProvider<?> getMojangMappedMinecraftProvider();
void setMojangMappedMinecraftProvider(MojangMappedMinecraftProvider<?> srgMinecraftProvider);
default List<Path> getMinecraftJars(MappingsNamespace mappingsNamespace) {
return switch (mappingsNamespace) {
case NAMED -> getNamedMinecraftProvider().getMinecraftJarPaths();
@@ -104,6 +109,10 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
ModPlatform.assertPlatform(this, ModPlatform.FORGE, () -> "SRG jars are only available on Forge.");
yield getSrgMinecraftProvider().getMinecraftJarPaths();
}
case MOJANG -> {
ModPlatform.assertPlatform(this, ModPlatform.NEOFORGE, () -> "Mojang-mapped jars are only available on NeoForge.");
yield getMojangMappedMinecraftProvider().getMinecraftJarPaths();
}
};
}
@@ -149,12 +158,12 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
return isForge() && !getForge().getDataGenMods().isEmpty();
}
default boolean isForgeAndOfficial() {
return isForge() && getMcpConfigProvider().isOfficial();
default boolean isForgeLikeAndOfficial() {
return isForgeLike() && getMcpConfigProvider().isOfficial();
}
default boolean isForgeAndNotOfficial() {
return isForge() && !getMcpConfigProvider().isOfficial();
default boolean isForgeLikeAndNotOfficial() {
return isForgeLike() && !getMcpConfigProvider().isOfficial();
}
DependencyProviders getDependencyProviders();
@@ -179,4 +188,14 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
ForgeRunsProvider getForgeRunsProvider();
void setForgeRunsProvider(ForgeRunsProvider forgeRunsProvider);
/**
* The mapping file that is specific to the platform settings.
* It contains SRG (Forge/common) or Mojang mappings (NeoForge) as needed.
*
* @return the platform mapping file path
*/
default Path getPlatformMappingFile() {
return getMappingConfiguration().getPlatformMappingFile(this);
}
}

View File

@@ -33,7 +33,7 @@ import org.gradle.api.provider.SetProperty;
import org.jetbrains.annotations.ApiStatus;
/**
* This is the forge extension api available exposed to build scripts.
* This is the Forge extension API available to build scripts.
*/
@ApiStatus.NonExtendable
public interface ForgeExtensionAPI {

View File

@@ -235,10 +235,18 @@ public interface LoomGradleExtensionAPI {
Provider<ModPlatform> getPlatform();
default boolean isForgeLike() {
return getPlatform().get().isForgeLike();
}
default boolean isForge() {
return getPlatform().get() == ModPlatform.FORGE;
}
default boolean isNeoForge() {
return getPlatform().get() == ModPlatform.NEOFORGE;
}
default boolean isQuilt() {
return getPlatform().get() == ModPlatform.QUILT;
}
@@ -265,4 +273,15 @@ public interface LoomGradleExtensionAPI {
ForgeExtensionAPI getForge();
void forge(Action<ForgeExtensionAPI> action);
/**
* Gets the NeoForge extension used to configure NeoForge details.
*
* @return the NeoForge extension
* @throws UnsupportedOperationException if running on another platform
* @see #isNeoForge()
*/
NeoForgeExtensionAPI getNeoForge();
void neoForge(Action<NeoForgeExtensionAPI> action);
}

View File

@@ -0,0 +1,50 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.api;
import org.gradle.api.file.ConfigurableFileCollection;
/**
* This is the NeoForge extension API available to build scripts.
*/
public interface NeoForgeExtensionAPI {
/**
* A collection of all project access transformers.
* The collection should only contain AT files, and not directories or other files.
*
* <p>If this collection is empty, Loom tries to resolve the AT from the default path
* ({@code META-INF/accesstransformer.cfg} in the {@code main} source set).
*
* @return the collection of AT files
*/
ConfigurableFileCollection getAccessTransformers();
/**
* Adds a {@linkplain #getAccessTransformers() project access transformer}.
*
* @param file the file, evaluated as per {@link org.gradle.api.Project#file(Object)}
*/
void accessTransformer(Object file);
}

View File

@@ -54,6 +54,14 @@ public enum MappingsNamespace {
*/
SRG,
/**
* Mojang's official names from their deobfuscation maps.
*
* <p>They are used as the mapping set in a NeoForge production environment akin to Fabric's
* {@linkplain #INTERMEDIARY intermediary mappings}.
*/
MOJANG,
/**
* Named mappings are the developer friendly names used to develop mods against.
*/
@@ -70,6 +78,7 @@ public enum MappingsNamespace {
case "official" -> OFFICIAL;
case "intermediary" -> INTERMEDIARY;
case "srg" -> SRG;
case "mojang" -> MOJANG;
case "named" -> NAMED;
default -> null;
};

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
* Copyright (c) 2022-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -34,8 +34,19 @@ public final class IntermediaryNamespaces {
* Returns the intermediary namespace of the project.
*/
public static String intermediary(Project project) {
return intermediaryNamespace(project).toString();
}
/**
* Returns the intermediary namespace of the project.
*/
public static MappingsNamespace intermediaryNamespace(Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
return extension.isForge() ? "srg" : "intermediary";
return switch (extension.getPlatform().get()) {
case FABRIC, QUILT -> MappingsNamespace.INTERMEDIARY;
case FORGE -> MappingsNamespace.SRG;
case NEOFORGE -> MappingsNamespace.MOJANG;
};
}
/**

View File

@@ -64,7 +64,7 @@ public class JarNester {
}
}).collect(Collectors.toList()));
if (platform == ModPlatform.FORGE) {
if (platform.isForgeLike()) {
handleForgeJarJar(forgeJars, modJar, logger);
return;
}

View File

@@ -36,6 +36,7 @@ import java.util.function.Consumer;
import javax.inject.Inject;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.AbstractCopyTask;
@@ -72,6 +73,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.AbstractMappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MojangMappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper;
@@ -132,7 +134,7 @@ public abstract class CompileConfiguration implements Runnable {
configureDecompileTasks(configContext);
if (extension.isForge()) {
if (extension.isForgeLike()) {
if (extension.isDataGenEnabled()) {
getProject().getExtensions().getByType(JavaPluginExtension.class).getSourceSets().getByName("main").resources(files -> {
files.srcDir(getProject().file("src/generated/resources"));
@@ -165,7 +167,7 @@ public abstract class CompileConfiguration implements Runnable {
getTasks().withType(AbstractCopyTask.class).configureEach(abstractCopyTask -> abstractCopyTask.setFilteringCharset(StandardCharsets.UTF_8.name()));
getTasks().withType(JavaCompile.class).configureEach(javaCompile -> javaCompile.getOptions().setEncoding(StandardCharsets.UTF_8.name()));
if (extension.isForge()) {
if (extension.isForgeLike()) {
// Create default mod from main source set
extension.mods(mods -> {
final SourceSet main = getProject().getExtensions().getByType(JavaPluginExtension.class).getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
@@ -188,7 +190,7 @@ public abstract class CompileConfiguration implements Runnable {
// Provide the vanilla mc jars -- TODO share across getProject()s.
final MinecraftProvider minecraftProvider = jarConfiguration.getMinecraftProviderFunction().apply(configContext);
if (extension.isForge() && !(minecraftProvider instanceof ForgeMinecraftProvider)) {
if (extension.isForgeLike() && !(minecraftProvider instanceof ForgeMinecraftProvider)) {
throw new UnsupportedOperationException("Using Forge with split jars is not supported!");
}
@@ -203,7 +205,7 @@ public abstract class CompileConfiguration implements Runnable {
final MappingConfiguration mappingConfiguration = MappingConfiguration.create(getProject(), configContext.serviceManager(), mappingsDep, minecraftProvider);
extension.setMappingConfiguration(mappingConfiguration);
if (extension.isForge()) {
if (extension.isForgeLike()) {
ForgeLibrariesProvider.provide(mappingConfiguration, project);
((ForgeMinecraftProvider) minecraftProvider).getPatchedProvider().provide();
}
@@ -211,7 +213,7 @@ public abstract class CompileConfiguration implements Runnable {
mappingConfiguration.setupPost(project);
mappingConfiguration.applyToProject(getProject(), mappingsDep);
if (extension.isForge()) {
if (extension.isForgeLike()) {
extension.setForgeRunsProvider(ForgeRunsProvider.create(project));
}
@@ -243,6 +245,10 @@ public abstract class CompileConfiguration implements Runnable {
final SrgMinecraftProvider<?> srgMinecraftProvider = jarConfiguration.getSrgMinecraftProviderBiFunction().apply(project, minecraftProvider);
extension.setSrgMinecraftProvider(srgMinecraftProvider);
srgMinecraftProvider.provide(provideContext);
} else if (extension.isNeoForge()) {
final MojangMappedMinecraftProvider<?> mojangMappedMinecraftProvider = jarConfiguration.getMojangMappedMinecraftProviderBiFunction().apply(project, minecraftProvider);
extension.setMojangMappedMinecraftProvider(mojangMappedMinecraftProvider);
mojangMappedMinecraftProvider.provide(provideContext);
}
}
@@ -262,8 +268,16 @@ public abstract class CompileConfiguration implements Runnable {
extension.addMinecraftJarProcessor(InterfaceInjectionProcessor.class, "fabric-loom:interface-inject", interfaceInjection.getEnableDependencyInterfaceInjection().get());
}
if (extension.isForge()) {
extension.addMinecraftJarProcessor(AccessTransformerJarProcessor.class, "loom:access-transformer", configContext.project(), extension.getForge().getAccessTransformers());
if (extension.isForgeLike()) {
FileCollection accessTransformers;
if (extension.isNeoForge()) {
accessTransformers = extension.getNeoForge().getAccessTransformers();
} else {
accessTransformers = extension.getForge().getAccessTransformers();
}
extension.addMinecraftJarProcessor(AccessTransformerJarProcessor.class, "loom:access-transformer", configContext.project(), accessTransformers);
}
}
@@ -356,7 +370,7 @@ public abstract class CompileConfiguration implements Runnable {
DependencyProviders dependencyProviders = new DependencyProviders();
extension.setDependencyProviders(dependencyProviders);
if (extension.isForge()) {
if (extension.isForgeLike()) {
dependencyProviders.addProvider(new ForgeProvider(project));
dependencyProviders.addProvider(new ForgeUserdevProvider(project));
}
@@ -365,7 +379,7 @@ public abstract class CompileConfiguration implements Runnable {
dependencyProviders.addProvider(new SrgProvider(project));
}
if (extension.isForge()) {
if (extension.isForgeLike()) {
dependencyProviders.addProvider(new McpConfigProvider(project));
dependencyProviders.addProvider(new PatchProvider(project));
dependencyProviders.addProvider(new ForgeUniversalProvider(project));

View File

@@ -118,9 +118,16 @@ public abstract class LoomConfigurations implements Runnable {
}
});
if (extension.isForge()) {
// Set up Forge configurations
registerNonTransitive(Constants.Configurations.FORGE, Role.RESOLVABLE);
if (extension.isForgeLike()) {
// Set up Forge and NeoForge configurations
if (extension.isForge()) {
// Forge-specific configurations
registerNonTransitive(Constants.Configurations.FORGE, Role.RESOLVABLE);
} else if (extension.isNeoForge()) {
// NeoForge-specific configurations
registerNonTransitive(Constants.Configurations.NEOFORGE, Role.RESOLVABLE);
}
registerNonTransitive(Constants.Configurations.FORGE_USERDEV, Role.RESOLVABLE);
registerNonTransitive(Constants.Configurations.FORGE_INSTALLER, Role.RESOLVABLE);
registerNonTransitive(Constants.Configurations.FORGE_UNIVERSAL, Role.RESOLVABLE);
@@ -150,10 +157,16 @@ public abstract class LoomConfigurations implements Runnable {
extendsFrom(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_EXTRA);
extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_EXTRA);
// Add Forge dev-time dependencies
getDependencies().add(Constants.Configurations.FORGE_EXTRA, LoomVersions.FORGE_RUNTIME.mavenNotation());
// Add Forge/NeoForge shared dev-time dependencies
getDependencies().add(Constants.Configurations.FORGE_EXTRA, LoomVersions.UNPROTECT.mavenNotation());
getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JAVAX_ANNOTATIONS.mavenNotation());
// Add Forge-only dev-time dependencies
if (extension.isForge()) {
getDependencies().add(Constants.Configurations.FORGE_EXTRA, LoomVersions.NAMING_SERVICE.mavenNotation());
getDependencies().add(Constants.Configurations.FORGE_EXTRA, LoomVersions.MIXIN_REMAPPER_SERVICE.mavenNotation());
getDependencies().add(Constants.Configurations.FORGE_EXTRA, LoomVersions.MCP_ANNOTATIONS.mavenNotation());
}
}
}

View File

@@ -37,14 +37,14 @@ public class LoomDependencyManager {
LoomGradleExtension extension = LoomGradleExtension.get(project);
SourceRemapper sourceRemapper = new SourceRemapper(project, serviceManager, true);
String platformSuffix = extension.isForge() ? "_forge" : extension.isQuilt() ? "_arch_quilt" : "";
String platformSuffix = extension.isForgeLike() ? "_forge" : extension.isQuilt() ? "_arch_quilt" : "";
String mappingsIdentifier = extension.getMappingConfiguration().mappingsIdentifier() + platformSuffix;
ModConfigurationRemapper.supplyModConfigurations(project, serviceManager, mappingsIdentifier, extension, sourceRemapper);
sourceRemapper.remapAll();
if (extension.getInstallerData() == null && !extension.isForge()) {
if (extension.getInstallerData() == null && !extension.isForgeLike()) {
if (extension.isQuilt()) {
project.getLogger().warn("quilt_installer.json not found in dependencies!");
} else {

View File

@@ -100,7 +100,7 @@ public abstract class MavenPublication implements Runnable {
if (hasSoftwareComponent(publication) || EXCLUDED_PUBLICATIONS.contains(publication)) {
continue;
} else if (!reportedDeprecation.get() && !LoomGradleExtension.get(getProject()).isForge()) {
} else if (!reportedDeprecation.get() && !LoomGradleExtension.get(getProject()).isForgeLike()) {
DeprecationHelper deprecationHelper = LoomGradleExtension.get(getProject()).getDeprecationHelper();
deprecationHelper.warn("Loom is applying dependency data manually to publications instead of using a software component (from(components[\"java\"])). This is deprecated.");
reportedDeprecation.set(true);

View File

@@ -54,6 +54,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.api.processor.MinecraftJarProcessor;
import net.fabricmc.loom.api.processor.ProcessorContext;
import net.fabricmc.loom.api.processor.SpecContext;
import net.fabricmc.loom.build.IntermediaryNamespaces;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DependencyDownloader;
import net.fabricmc.loom.util.ExceptionUtil;
@@ -138,7 +139,7 @@ public class AccessTransformerJarProcessor implements MinecraftJarProcessor<Acce
}
}
accessTransformSet = accessTransformSet.remap(new TinyMappingsReader(context.getMappings(), MappingsNamespace.SRG.toString(), MappingsNamespace.NAMED.toString()).read());
accessTransformSet = accessTransformSet.remap(new TinyMappingsReader(context.getMappings(), IntermediaryNamespaces.intermediary(project), MappingsNamespace.NAMED.toString()).read());
final Path accessTransformerPath = tempFiles.file("accesstransformer-merged", ".cfg");

View File

@@ -80,7 +80,7 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration<Mapp
task.setDescription("Decompile Minecraft using Forge's toolchain.");
task.setGroup(Constants.TaskGroup.FABRIC);
task.getInputJar().set(MinecraftPatchedProvider.get(project).getMinecraftSrgJar().toFile());
task.getInputJar().set(MinecraftPatchedProvider.get(project).getMinecraftIntermediateJar().toFile());
task.getRuntimeJar().set(minecraftJar.toFile());
});
}

View File

@@ -345,7 +345,7 @@ public class RunConfigSettings implements Named {
environment("client");
defaultMainClass(Constants.Knot.KNOT_CLIENT);
if (getExtension().isForge()) {
if (getExtension().isForgeLike()) {
forgeTemplate("client");
}
}
@@ -358,7 +358,7 @@ public class RunConfigSettings implements Named {
environment("server");
defaultMainClass(Constants.Knot.KNOT_SERVER);
if (getExtension().isForge()) {
if (getExtension().isForgeLike()) {
forgeTemplate("server");
}
}
@@ -369,7 +369,7 @@ public class RunConfigSettings implements Named {
* <p>This method can only be used on Forge.
*/
public void data() {
ModPlatform.assertPlatform(getExtension(), ModPlatform.FORGE, () -> "RunConfigSettings.data() is only usable on Forge.");
ModPlatform.assertForgeLike(getExtension(), () -> "RunConfigSettings.data() is only usable on Forge.");
environment("data");
forgeTemplate("data");
}
@@ -384,7 +384,7 @@ public class RunConfigSettings implements Named {
* @since 1.0
*/
public void forgeTemplate(String templateName) {
ModPlatform.assertPlatform(getExtension(), ModPlatform.FORGE);
ModPlatform.assertForgeLike(getExtension());
defaultMainClass(Constants.Forge.UNDETERMINED_MAIN_CLASS);
// Evaluate later if Forge hasn't been resolved yet.
evaluateNowOrLater(() -> {
@@ -437,11 +437,11 @@ public class RunConfigSettings implements Named {
* {@linkplain net.fabricmc.loom.api.LoomGradleExtensionAPI#getMods global container}
* declared in the {@code loom} extension.
*
* <p>This method is currently only available on Forge.
* <p>This method is currently only available on Forge and NeoForge.
*/
@ApiStatus.Experimental
public NamedDomainObjectContainer<ModSettings> getMods() {
ModPlatform.assertPlatform(project, ModPlatform.FORGE);
ModPlatform.assertForgeLike(extension);
return mods;
}

View File

@@ -60,8 +60,8 @@ public record ArtifactMetadata(boolean isFabricMod, RemapRequirements remapRequi
RemapRequirements remapRequirements = RemapRequirements.DEFAULT;
InstallerData installerData = null;
// Force-remap all mods on Forge.
if (platform == ModPlatform.FORGE) {
// Force-remap all mods on Forge and NeoForge.
if (platform.isForgeLike()) {
remapRequirements = RemapRequirements.OPT_IN;
}

View File

@@ -41,10 +41,12 @@ import java.util.regex.Pattern;
import com.google.common.base.Stopwatch;
import com.google.gson.JsonObject;
import dev.architectury.loom.util.MappingOption;
import dev.architectury.tinyremapper.InputTag;
import dev.architectury.tinyremapper.NonClassCopyMode;
import dev.architectury.tinyremapper.OutputConsumerPath;
import dev.architectury.tinyremapper.TinyRemapper;
import dev.architectury.tinyremapper.extension.mixin.MixinExtension;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.attributes.Usage;
@@ -166,8 +168,15 @@ public class ModProcessor {
Stopwatch stopwatch = Stopwatch.createStarted();
boolean srg = (fromM.equals("srg") || toM.equals("srg")) && extension.isForge();
MemoryMappingTree mappings = mappingConfiguration.getMappingsService(serviceManager, srg).getMappingTree();
MappingOption mappingOption = MappingOption.DEFAULT;
if ((fromM.equals(MappingsNamespace.SRG.toString()) || toM.equals(MappingsNamespace.SRG.toString())) && extension.isForge()) {
mappingOption = MappingOption.WITH_SRG;
} else if ((fromM.equals(MappingsNamespace.MOJANG.toString()) || toM.equals(MappingsNamespace.MOJANG.toString())) && extension.isNeoForge()) {
mappingOption = MappingOption.WITH_MOJANG;
}
MemoryMappingTree mappings = mappingConfiguration.getMappingsService(serviceManager, mappingOption).getMappingTree();
LoggerFilter.replaceSystemOut();
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
.withKnownIndyBsm(extension.getKnownIndyBsms().get())
@@ -185,9 +194,13 @@ public class ModProcessor {
builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension());
}
if (extension.isNeoForge()) {
builder.extension(new MixinExtension());
}
final TinyRemapper remapper = builder.build();
for (Path minecraftJar : extension.getMinecraftJars(extension.isForge() ? MappingsNamespace.SRG : MappingsNamespace.INTERMEDIARY)) {
for (Path minecraftJar : extension.getMinecraftJars(IntermediaryNamespaces.intermediaryNamespace(project))) {
remapper.readClassPathAsync(minecraftJar);
}
@@ -264,9 +277,9 @@ public class ModProcessor {
stripNestedJars(output);
remapJarManifestEntries(output);
if (extension.isForge()) {
AtRemapper.remap(project.getLogger(), output, mappings);
CoreModClassRemapper.remapJar(output, mappings, project.getLogger());
if (extension.isForgeLike()) {
AtRemapper.remap(project, output, mappings);
CoreModClassRemapper.remapJar(project, extension.getPlatform().get(), output, mappings);
}
dependency.copyToCache(project, output, null);

View File

@@ -24,6 +24,7 @@
package net.fabricmc.loom.configuration.processors;
import dev.architectury.loom.util.MappingOption;
import dev.architectury.tinyremapper.TinyRemapper;
import net.fabricmc.loom.LoomGradleExtension;
@@ -64,6 +65,7 @@ public record ProcessorContextImpl(ConfigContext configContext, MinecraftJar min
@Override
public MemoryMappingTree getMappings() {
LoomGradleExtension extension = LoomGradleExtension.get(configContext().project());
return extension.getMappingConfiguration().getMappingsService(configContext().serviceManager(), extension.isForge()).getMappingTree();
final MappingOption mappingOption = MappingOption.forPlatform(extension);
return extension.getMappingConfiguration().getMappingsService(configContext().serviceManager(), mappingOption).getMappingTree();
}
}

View File

@@ -24,6 +24,8 @@
package net.fabricmc.loom.configuration.providers.forge;
import com.mojang.serialization.Codec;
/**
* A string or a variable in a Forge configuration file, or an MCPConfig step or function.
*/
@@ -41,6 +43,16 @@ public sealed interface ConfigValue {
*/
String LOG = "log";
Codec<ConfigValue> CODEC = Codec.STRING.xmap(ConfigValue::of, configValue -> {
if (configValue instanceof Constant constant) {
return constant.value();
} else if (configValue instanceof Variable variable) {
return "{" + variable.name() + "}";
}
throw new IllegalArgumentException("Unmatched config value");
});
String resolve(Resolver variableResolver);
static ConfigValue of(String str) {

View File

@@ -66,8 +66,9 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class FieldMigratedMappingConfiguration extends MappingConfiguration {
private List<Map.Entry<FieldMember, String>> migratedFields = new ArrayList<>();
public Path migratedFieldsCache;
public Path rawTinyMappings;
public Path rawTinyMappingsWithSrg;
private Path rawTinyMappings;
private Path rawTinyMappingsWithSrg;
private Path rawTinyMappingsWithMojang;
public FieldMigratedMappingConfiguration(String mappingsIdentifier, Path mappingsWorkingDir) {
super(mappingsIdentifier, mappingsWorkingDir);
@@ -98,7 +99,10 @@ public final class FieldMigratedMappingConfiguration extends MappingConfiguratio
}
public static String createForgeMappingsIdentifier(LoomGradleExtension extension, String mappingsName, String version, String classifier, String minecraftVersion) {
return FieldMigratedMappingConfiguration.createMappingsIdentifier(mappingsName, version, classifier, minecraftVersion) + "-forge-" + extension.getForgeProvider().getVersion().getCombined();
final String base = FieldMigratedMappingConfiguration.createMappingsIdentifier(mappingsName, version, classifier, minecraftVersion);
final String platform = extension.getPlatform().get().id();
final String forgeVersion = extension.getForgeProvider().getVersion().getCombined();
return base + "-" + platform + "-" + forgeVersion;
}
@Override
@@ -107,23 +111,31 @@ public final class FieldMigratedMappingConfiguration extends MappingConfiguratio
LoomGradleExtension extension = LoomGradleExtension.get(project);
this.rawTinyMappings = tinyMappings;
this.rawTinyMappingsWithSrg = tinyMappingsWithSrg;
this.rawTinyMappingsWithMojang = tinyMappingsWithMojang;
tinyMappings = mappingsWorkingDir().resolve("mappings-field-migrated.tiny");
tinyMappingsWithSrg = mappingsWorkingDir().resolve("mappings-srg-field-migrated.tiny");
tinyMappingsWithMojang = mappingsWorkingDir().resolve("mappings-mojang-field-migrated.tiny");
try {
updateFieldMigration(project);
updateFieldMigration(project, extension.isNeoForge(), extension.shouldGenerateSrgTiny());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
project.getLogger().info(":migrated srg fields in " + stopwatch.stop());
project.getLogger().info(":migrated {} fields in " + stopwatch.stop(), extension.getPlatform().get().id());
}
public void updateFieldMigration(Project project) throws IOException {
public void updateFieldMigration(Project project, boolean hasMojang, boolean hasSrg) throws IOException {
if (!Files.exists(migratedFieldsCache)) {
migratedFields.clear();
migratedFields.addAll(generateNewFieldMigration(project, MinecraftPatchedProvider.get(project).getMinecraftPatchedSrgJar(), MappingsNamespace.SRG.toString(), rawTinyMappingsWithSrg).entrySet());
if (hasSrg) {
migratedFields.addAll(generateNewFieldMigration(project, MinecraftPatchedProvider.get(project).getMinecraftPatchedIntermediateJar(), MappingsNamespace.SRG.toString(), rawTinyMappingsWithSrg).entrySet());
} else if (hasMojang) {
migratedFields.addAll(generateNewFieldMigration(project, MinecraftPatchedProvider.get(project).getMinecraftPatchedIntermediateJar(), MappingsNamespace.MOJANG.toString(), rawTinyMappingsWithMojang).entrySet());
}
Map<String, String> map = new HashMap<>();
migratedFields.forEach(entry -> {
map.put(entry.getKey().owner + "#" + entry.getKey().field, entry.getValue());
@@ -131,9 +143,10 @@ public final class FieldMigratedMappingConfiguration extends MappingConfiguratio
Files.writeString(migratedFieldsCache, new Gson().toJson(map));
Files.deleteIfExists(tinyMappings);
Files.deleteIfExists(tinyMappingsWithSrg);
Files.deleteIfExists(tinyMappingsWithMojang);
}
if (Files.notExists(tinyMappings) || Files.notExists(tinyMappingsWithSrg)) {
if (Files.notExists(tinyMappings) || (hasSrg && Files.notExists(tinyMappingsWithSrg)) || (hasMojang && Files.notExists(tinyMappingsWithMojang))) {
Table<String, String, String> fieldDescriptorMap = HashBasedTable.create();
for (Map.Entry<FieldMember, String> entry : migratedFields) {
@@ -141,7 +154,12 @@ public final class FieldMigratedMappingConfiguration extends MappingConfiguratio
}
injectMigration(project, fieldDescriptorMap, rawTinyMappings, tinyMappings);
injectMigration(project, fieldDescriptorMap, rawTinyMappingsWithSrg, tinyMappingsWithSrg);
if (hasSrg) {
injectMigration(project, fieldDescriptorMap, rawTinyMappingsWithSrg, tinyMappingsWithSrg);
} else if (hasMojang) {
injectMigration(project, fieldDescriptorMap, rawTinyMappingsWithMojang, tinyMappingsWithMojang);
}
}
}

View File

@@ -32,7 +32,6 @@ import java.util.ArrayList;
import java.util.List;
import com.google.common.hash.Hashing;
import com.google.gson.JsonElement;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ModuleDependency;
@@ -41,16 +40,18 @@ import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedConfiguration;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.mods.ModConfigurationRemapper;
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.PropertyUtil;
import net.fabricmc.loom.util.srg.RemapObjectHolderVisitor;
import net.fabricmc.loom.util.srg.SrgMerger;
import net.fabricmc.loom.util.srg.ForgeMappingsMerger;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class ForgeLibrariesProvider {
@@ -64,21 +65,31 @@ public class ForgeLibrariesProvider {
final List<Dependency> dependencies = new ArrayList<>();
// Collect all dependencies with possible relocations, such as Mixin.
for (JsonElement lib : extension.getForgeUserdevProvider().getJson().get("libraries").getAsJsonArray()) {
for (String lib : extension.getForgeUserdevProvider().getConfig().libraries()) {
String dep = null;
if (lib.getAsString().startsWith("org.spongepowered:mixin:")) {
if (PropertyUtil.getAndFinalize(extension.getForge().getUseCustomMixin())) {
if (lib.getAsString().contains("0.8.2")) {
if (lib.startsWith("org.spongepowered:mixin:")) {
// Don't apply custom mixin on NeoForge.
if (extension.isForge() && PropertyUtil.getAndFinalize(extension.getForge().getUseCustomMixin())) {
if (lib.contains("0.8.2")) {
dep = "net.fabricmc:sponge-mixin:0.8.2+build.24";
} else {
dep = "dev.architectury:mixin-patched" + lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+";
String version = lib.substring(lib.lastIndexOf(":"));
// Used for the file extension, for example @jar
int atIndex = version.indexOf('@');
if (atIndex >= 0) {
// Strip the file extension away
version = version.substring(0, atIndex);
}
dep = "dev.architectury:mixin-patched" + version + ".+";
}
}
}
if (dep == null) {
dep = lib.getAsString();
dep = lib;
}
dependencies.add(project.getDependencies().create(dep));
@@ -158,6 +169,10 @@ public class ForgeLibrariesProvider {
if (Files.exists(fs.get().getPath("net/minecraftforge/fml/common/asm/ObjectHolderDefinalize.class"))) {
remapObjectHolder(project, outputJar, mappingConfiguration);
}
if (Files.exists(fs.getPath("net/neoforged/fml/common/asm/ObjectHolderDefinalize.class"))) {
remapNeoForgeObjectHolder(project, outputJar, mappingConfiguration);
}
}
// Copy sources when not running under CI.
@@ -178,8 +193,8 @@ public class ForgeLibrariesProvider {
// Merge SRG mappings. The real SRG mapping file hasn't been created yet since the usual SRG merging
// process occurs after all Forge libraries have been provided.
// Forge libs are needed for MC, which is needed for the mappings.
final SrgMerger.ExtraMappings extraMappings = SrgMerger.ExtraMappings.ofMojmapTsrg(MappingConfiguration.getMojmapSrgFileIfPossible(project));
final MemoryMappingTree mappings = SrgMerger.mergeSrg(MappingConfiguration.getRawSrgFile(project), mappingConfiguration.tinyMappings, extraMappings, true);
final ForgeMappingsMerger.ExtraMappings extraMappings = ForgeMappingsMerger.ExtraMappings.ofMojmapTsrg(MappingConfiguration.getMojmapSrgFileIfPossible(project));
final MemoryMappingTree mappings = ForgeMappingsMerger.mergeSrg(MappingConfiguration.getRawSrgFile(project), mappingConfiguration.tinyMappings, extraMappings, true);
// Remap the object holders.
RemapObjectHolderVisitor.remapObjectHolder(
@@ -191,6 +206,24 @@ public class ForgeLibrariesProvider {
}
}
private static void remapNeoForgeObjectHolder(Project project, Path outputJar, MappingConfiguration mappingConfiguration) throws IOException {
try {
// Merge Mojang mappings. The real Mojang mapping file hasn't been created yet since the usual Mojang merging
// process occurs after all Forge libraries have been provided.
// Forge libs are needed for MC, which is needed for the mappings.
final MappingContext context = new GradleMappingContext(project, "tmp-neoforge-libs");
final MemoryMappingTree mappings = ForgeMappingsMerger.mergeMojang(context, mappingConfiguration.tinyMappings, null, true);
// Remap the object holders.
RemapObjectHolderVisitor.remapObjectHolder(
outputJar, "net.neoforged.fml.common.asm.ObjectHolderDefinalize", mappings,
MappingsNamespace.MOJANG.toString(), MappingsNamespace.NAMED.toString()
);
} catch (IOException e) {
throw new IOException("Could not remap object holders in " + outputJar, e);
}
}
/**
* Reconstructs the dependency notation of a resolved artifact.
* @param artifact the artifact

View File

@@ -32,14 +32,16 @@ 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.ModPlatform;
public class ForgeProvider extends DependencyProvider {
private final ModPlatform platform;
private ForgeVersion version = new ForgeVersion(null);
private File globalCache;
private File projectCache;
public ForgeProvider(Project project) {
super(project);
platform = getExtension().getPlatform().get();
}
@Override
@@ -55,25 +57,16 @@ public class ForgeProvider extends DependencyProvider {
public File getGlobalCache() {
if (globalCache == null) {
globalCache = getMinecraftProvider().dir("forge/" + version.getCombined());
globalCache = getMinecraftProvider().dir(platform.id() + "/" + version.getCombined());
globalCache.mkdirs();
}
return globalCache;
}
public File getProjectCache() {
if (projectCache == null) {
projectCache = new File(getDirectories().getRootProjectPersistentCache(), getMinecraftProvider().minecraftVersion() + "/forge/" + getExtension().getForgeProvider().getVersion().getCombined() + "/project-" + getProject().getPath().replace(':', '@'));
projectCache.mkdirs();
}
return projectCache;
}
@Override
public String getTargetConfig() {
return Constants.Configurations.FORGE;
return platform == ModPlatform.NEOFORGE ? Constants.Configurations.NEOFORGE : Constants.Configurations.FORGE;
}
/**
@@ -83,9 +76,10 @@ public class ForgeProvider extends DependencyProvider {
*/
public static Path getForgeCache(Project project) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final ModPlatform platform = extension.getPlatform().get();
final String version = extension.getForgeProvider().getVersion().getCombined();
return LoomGradleExtension.get(project).getMinecraftProvider()
.dir("forge/" + version).toPath();
.dir(platform.id() + "/" + version).toPath();
}
public static final class ForgeVersion {

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
* Copyright (c) 2022-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,18 +24,17 @@
package net.fabricmc.loom.configuration.providers.forge;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import org.gradle.api.Named;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Pair;
import net.fabricmc.loom.util.function.CollectionUtil;
public record ForgeRunTemplate(
@@ -46,6 +45,51 @@ public record ForgeRunTemplate(
Map<String, ConfigValue> env,
Map<String, ConfigValue> props
) implements Named {
public static final Codec<ForgeRunTemplate> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.optionalFieldOf("name", "") // note: empty is used since DFU crashes with null
.forGetter(ForgeRunTemplate::name),
Codec.STRING.fieldOf("main")
.forGetter(ForgeRunTemplate::main),
ConfigValue.CODEC.listOf().optionalFieldOf("args", List.of())
.forGetter(ForgeRunTemplate::args),
ConfigValue.CODEC.listOf().optionalFieldOf("jvmArgs", List.of())
.forGetter(ForgeRunTemplate::jvmArgs),
Codec.unboundedMap(Codec.STRING, ConfigValue.CODEC).optionalFieldOf("env", Map.of())
.forGetter(ForgeRunTemplate::env),
Codec.unboundedMap(Codec.STRING, ConfigValue.CODEC).optionalFieldOf("props", Map.of())
.forGetter(ForgeRunTemplate::props)
).apply(instance, ForgeRunTemplate::new));
public static final Codec<Map<String, ForgeRunTemplate>> MAP_CODEC = Codec.unboundedMap(Codec.STRING, CODEC)
.xmap(
map -> {
final Map<String, ForgeRunTemplate> newMap = new HashMap<>(map);
// Iterate through all templates and fill in empty names.
// The NeoForge format doesn't include the name property, so we'll use the map keys
// as a replacement.
for (Map.Entry<String, ForgeRunTemplate> entry : newMap.entrySet()) {
final ForgeRunTemplate template = entry.getValue();
if (template.name.isEmpty()) {
final ForgeRunTemplate completed = new ForgeRunTemplate(
entry.getKey(),
template.main,
template.args,
template.jvmArgs,
template.env,
template.props
);
entry.setValue(completed);
}
}
return newMap;
},
Function.identity()
);
@Override
public String getName() {
return name;
@@ -66,29 +110,4 @@ public record ForgeRunTemplate(
// Add MOD_CLASSES, this is something that ForgeGradle does
settings.getEnvironmentVariables().computeIfAbsent("MOD_CLASSES", $ -> ConfigValue.of("{source_roots}").resolve(configValueResolver));
}
public static ForgeRunTemplate fromJson(JsonObject json) {
if (json.has("parents") && !json.getAsJsonArray("parents").isEmpty()) {
throw new IllegalArgumentException("Non-empty parents for run config template not supported!");
}
String name = json.getAsJsonPrimitive("name").getAsString();
String main = json.getAsJsonPrimitive("main").getAsString();
List<ConfigValue> args = json.has("args") ? fromJson(json.getAsJsonArray("args")) : List.of();
List<ConfigValue> jvmArgs = json.has("jvmArgs") ? fromJson(json.getAsJsonArray("jvmArgs")) : List.of();
Map<String, ConfigValue> env = json.has("env") ? fromJson(json.getAsJsonObject("env"), ConfigValue::of) : Map.of();
Map<String, ConfigValue> props = json.has("props") ? fromJson(json.getAsJsonObject("props"), ConfigValue::of) : Map.of();
return new ForgeRunTemplate(name, main, args, jvmArgs, env, props);
}
private static List<ConfigValue> fromJson(JsonArray json) {
return CollectionUtil.map(json, child -> ConfigValue.of(child.getAsJsonPrimitive().getAsString()));
}
private static <R> Map<String, R> fromJson(JsonObject json, Function<String, R> converter) {
return json.entrySet().stream().map(entry -> {
String value = entry.getValue().getAsJsonPrimitive().getAsString();
return new Pair<>(entry.getKey(), converter.apply(value));
}).collect(Collectors.toMap(Pair::left, Pair::right));
}
}

View File

@@ -29,7 +29,6 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -39,6 +38,7 @@ import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import dev.architectury.loom.forge.UserdevConfig;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.NamedDomainObjectSet;
import org.gradle.api.Project;
@@ -58,19 +58,12 @@ public class ForgeRunsProvider {
private final JsonObject json;
private final NamedDomainObjectSet<ForgeRunTemplate> templates;
public ForgeRunsProvider(Project project, JsonObject json) {
public ForgeRunsProvider(Project project, JsonObject json, UserdevConfig userdevConfig) {
this.project = project;
this.extension = LoomGradleExtension.get(project);
this.json = json;
this.templates = project.getObjects().namedDomainObjectSet(ForgeRunTemplate.class);
readTemplates();
}
private void readTemplates() {
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject("runs").entrySet()) {
ForgeRunTemplate template = ForgeRunTemplate.fromJson(entry.getValue().getAsJsonObject());
templates.add(template);
}
this.templates.addAll(userdevConfig.runs().values());
}
public NamedDomainObjectSet<ForgeRunTemplate> getTemplates() {
@@ -78,8 +71,8 @@ public class ForgeRunsProvider {
}
public static ForgeRunsProvider create(Project project) {
JsonObject json = LoomGradleExtension.get(project).getForgeUserdevProvider().getJson();
return new ForgeRunsProvider(project, json);
final ForgeUserdevProvider userdevProvider = LoomGradleExtension.get(project).getForgeUserdevProvider();
return new ForgeRunsProvider(project, userdevProvider.getJson(), userdevProvider.getConfig());
}
public ConfigValue.Resolver getResolver(@Nullable RunConfigSettings runConfig) {

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2020-2022 FabricMC
* Copyright (c) 2020-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -29,13 +29,13 @@ import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.mojang.serialization.JsonOps;
import dev.architectury.loom.forge.UserdevConfig;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.DependencyInfo;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ZipUtils;
@@ -43,8 +43,8 @@ import net.fabricmc.loom.util.ZipUtils;
public class ForgeUserdevProvider extends DependencyProvider {
private File userdevJar;
private JsonObject json;
private UserdevConfig config;
Path joinedPatches;
BinaryPatcherConfig binaryPatcherConfig;
public ForgeUserdevProvider(Project project) {
super(project);
@@ -64,17 +64,21 @@ public class ForgeUserdevProvider extends DependencyProvider {
try (Reader reader = Files.newBufferedReader(configJson)) {
json = new Gson().fromJson(reader, JsonObject.class);
config = UserdevConfig.CODEC.parse(JsonOps.INSTANCE, json)
.getOrThrow(false, msg -> getProject().getLogger().error("Couldn't read userdev config, {}", msg));
}
addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG);
addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG);
addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL);
addDependency(config.mcp(), Constants.Configurations.MCP_CONFIG);
if (!getExtension().isNeoForge()) {
addDependency(config.mcp(), Constants.Configurations.SRG);
}
addDependency(config.universal(), Constants.Configurations.FORGE_UNIVERSAL);
if (Files.notExists(joinedPatches)) {
Files.write(joinedPatches, ZipUtils.unpack(userdevJar.toPath(), json.get("binpatches").getAsString()));
Files.write(joinedPatches, ZipUtils.unpack(userdevJar.toPath(), config.binpatches()));
}
binaryPatcherConfig = BinaryPatcherConfig.fromJson(json.getAsJsonObject("binpatcher"));
}
public File getUserdevJar() {
@@ -90,11 +94,7 @@ public class ForgeUserdevProvider extends DependencyProvider {
return json;
}
public record BinaryPatcherConfig(String dependency, List<String> args) {
public static BinaryPatcherConfig fromJson(JsonObject json) {
String dependency = json.get("version").getAsString();
List<String> args = List.of(LoomGradlePlugin.GSON.fromJson(json.get("args"), String[].class));
return new BinaryPatcherConfig(dependency, args);
}
public UserdevConfig getConfig() {
return config;
}
}

View File

@@ -52,6 +52,8 @@ import java.util.stream.Stream;
import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer;
import dev.architectury.loom.forge.UserdevConfig;
import dev.architectury.loom.util.MappingOption;
import dev.architectury.loom.util.TempFiles;
import dev.architectury.tinyremapper.InputTag;
import dev.architectury.tinyremapper.NonClassCopyMode;
@@ -69,6 +71,7 @@ import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.build.IntermediaryNamespaces;
import net.fabricmc.loom.configuration.accesstransformer.AccessTransformerJarProcessor;
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpConfigProvider;
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.McpExecutor;
@@ -86,12 +89,14 @@ import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.function.FsPathConsumer;
import net.fabricmc.loom.util.service.ScopedSharedServiceManager;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.loom.util.srg.CoreModClassRemapper;
import net.fabricmc.loom.util.srg.InnerClassRemapper;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class MinecraftPatchedProvider {
private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version";
private static final String CURRENT_LOOM_PATCH_VERSION = "8";
private static final String CURRENT_LOOM_PATCH_VERSION = "9";
private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService";
private final Project project;
@@ -99,12 +104,12 @@ public class MinecraftPatchedProvider {
private final MinecraftProvider minecraftProvider;
private final Type type;
// Step 1: Remap Minecraft to SRG, merge if needed
private Path minecraftSrgJar;
// Step 1: Remap Minecraft to intermediate mappings, merge if needed
private Path minecraftIntermediateJar;
// Step 2: Binary Patch
private Path minecraftPatchedSrgJar;
private Path minecraftPatchedIntermediateJar;
// Step 3: Access Transform
private Path minecraftPatchedSrgAtJar;
private Path minecraftPatchedIntermediateAtJar;
// Step 4: Remap Patched AT & Forge to official
private Path minecraftPatchedJar;
private Path minecraftClientExtra;
@@ -135,13 +140,16 @@ public class MinecraftPatchedProvider {
private void initPatchedFiles() {
String forgeVersion = getExtension().getForgeProvider().getVersion().getCombined();
Path forgeWorkingDir = ForgeProvider.getForgeCache(project);
String patchId = "forge-" + forgeVersion + "-";
// Note: strings used instead of platform id since FML requires one of these exact strings
// depending on the loader to recognise Minecraft.
String patchId = (getExtension().isNeoForge() ? "neoforge" : "forge") + "-" + forgeVersion + "-";
minecraftProvider.setJarPrefix(patchId);
minecraftSrgJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-srg.jar");
minecraftPatchedSrgJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-srg-patched.jar");
minecraftPatchedSrgAtJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-srg-at-patched.jar");
final String intermediateId = getExtension().isNeoForge() ? "mojang" : "srg";
minecraftIntermediateJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-" + intermediateId + ".jar");
minecraftPatchedIntermediateJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-" + intermediateId + "-patched.jar");
minecraftPatchedIntermediateAtJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-" + intermediateId + "-at-patched.jar");
minecraftPatchedJar = forgeWorkingDir.resolve("minecraft-" + type.id + "-patched.jar");
minecraftClientExtra = forgeWorkingDir.resolve("client-extra.jar");
}
@@ -154,9 +162,9 @@ public class MinecraftPatchedProvider {
private Path[] getGlobalCaches() {
Path[] files = {
minecraftSrgJar,
minecraftPatchedSrgJar,
minecraftPatchedSrgAtJar,
minecraftIntermediateJar,
minecraftPatchedIntermediateJar,
minecraftPatchedIntermediateAtJar,
minecraftPatchedJar,
minecraftClientExtra,
};
@@ -177,22 +185,22 @@ public class MinecraftPatchedProvider {
this.dirty = false;
if (Files.notExists(minecraftSrgJar)) {
if (Files.notExists(minecraftIntermediateJar)) {
this.dirty = true;
try (var tempFiles = new TempFiles()) {
McpExecutor executor = createMcpExecutor(tempFiles.directory("loom-mcp"));
Path output = executor.enqueue("rename").execute();
Files.copy(output, minecraftSrgJar);
Files.copy(output, minecraftIntermediateJar);
}
}
if (dirty || Files.notExists(minecraftPatchedSrgJar)) {
if (dirty || Files.notExists(minecraftPatchedIntermediateJar)) {
this.dirty = true;
patchJars();
}
if (dirty || Files.notExists(minecraftPatchedSrgAtJar)) {
if (dirty || Files.notExists(minecraftPatchedIntermediateAtJar)) {
this.dirty = true;
accessTransformForge();
}
@@ -207,7 +215,6 @@ public class MinecraftPatchedProvider {
fillClientExtraJar();
}
this.dirty = false;
DependencyProvider.addDependency(project, minecraftClientExtra, Constants.Configurations.FORGE_EXTRA);
}
@@ -220,14 +227,16 @@ public class MinecraftPatchedProvider {
private TinyRemapper buildRemapper(SharedServiceManager serviceManager, Path input) throws IOException {
Path[] libraries = TinyRemapperHelper.getMinecraftCompileLibraries(project);
TinyMappingsService mappingsService = getExtension().getMappingConfiguration().getMappingsService(serviceManager, true);
MemoryMappingTree mappingsWithSrg = mappingsService.getMappingTree();
final MappingOption mappingOption = MappingOption.forPlatform(getExtension());
TinyMappingsService mappingsService = getExtension().getMappingConfiguration().getMappingsService(serviceManager, mappingOption);
final String sourceNamespace = IntermediaryNamespaces.intermediary(project);
MemoryMappingTree mappings = mappingsService.getMappingTree();
TinyRemapper remapper = TinyRemapper.newRemapper()
.logger(logger::lifecycle)
.logUnknownInvokeDynamic(false)
.withMappings(TinyRemapperHelper.create(mappingsWithSrg, "srg", "official", true))
.withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, "srg", "official"))
.withMappings(TinyRemapperHelper.create(mappings, sourceNamespace, "official", true))
.withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappings, sourceNamespace, "official"))
.renameInvalidLocals(true)
.rebuildSourceFilenames(true)
.build();
@@ -359,8 +368,8 @@ public class MinecraftPatchedProvider {
}
private void accessTransformForge() throws IOException {
Path input = minecraftPatchedSrgJar;
Path target = minecraftPatchedSrgAtJar;
Path input = minecraftPatchedIntermediateJar;
Path target = minecraftPatchedIntermediateAtJar;
accessTransform(project, input, target);
}
@@ -375,7 +384,7 @@ public class MinecraftPatchedProvider {
extension.getForgeUserdevProvider().getUserdevJar().toPath(),
((ForgeMinecraftProvider) extension.getMinecraftProvider())
.getPatchedProvider()
.getMinecraftPatchedSrgJar()
.getMinecraftPatchedIntermediateJar()
);
Files.deleteIfExists(target);
@@ -400,7 +409,7 @@ public class MinecraftPatchedProvider {
private void remapPatchedJar(SharedServiceManager serviceManager) throws Exception {
logger.lifecycle(":remapping minecraft (TinyRemapper, srg -> official)");
Path mcInput = minecraftPatchedSrgAtJar;
Path mcInput = minecraftPatchedIntermediateAtJar;
Path mcOutput = minecraftPatchedJar;
Path forgeJar = getForgeJar().toPath();
Path forgeUserdevJar = getForgeUserdevJar().toPath();
@@ -425,19 +434,27 @@ public class MinecraftPatchedProvider {
}
copyUserdevFiles(forgeUserdevJar, mcOutput);
remapCoreMods(mcOutput, serviceManager);
applyLoomPatchVersion(mcOutput);
}
private void remapCoreMods(Path patchedJar, SharedServiceManager serviceManager) throws Exception {
final MappingOption mappingOption = MappingOption.forPlatform(getExtension());
final TinyMappingsService mappingsService = getExtension().getMappingConfiguration().getMappingsService(serviceManager, mappingOption);
final MappingTree mappings = mappingsService.getMappingTree();
CoreModClassRemapper.remapJar(project, getExtension().getPlatform().get(), patchedJar, mappings);
}
private void patchJars() throws Exception {
Stopwatch stopwatch = Stopwatch.createStarted();
logger.lifecycle(":patching jars");
patchJars(minecraftSrgJar, minecraftPatchedSrgJar, type.patches.apply(getExtension().getPatchProvider(), getExtension().getForgeUserdevProvider()));
patchJars(minecraftIntermediateJar, minecraftPatchedIntermediateJar, type.patches.apply(getExtension().getPatchProvider(), getExtension().getForgeUserdevProvider()));
copyMissingClasses(minecraftSrgJar, minecraftPatchedSrgJar);
deleteParameterNames(minecraftPatchedSrgJar);
copyMissingClasses(minecraftIntermediateJar, minecraftPatchedIntermediateJar);
deleteParameterNames(minecraftPatchedIntermediateJar);
if (getExtension().isForgeAndNotOfficial()) {
fixParameterAnnotation(minecraftPatchedSrgJar);
if (getExtension().isForgeLikeAndNotOfficial()) {
fixParameterAnnotation(minecraftPatchedIntermediateJar);
}
logger.lifecycle(":patched jars in " + stopwatch.stop());
@@ -445,7 +462,7 @@ public class MinecraftPatchedProvider {
private void patchJars(Path clean, Path output, Path patches) {
ForgeToolExecutor.exec(project, spec -> {
ForgeUserdevProvider.BinaryPatcherConfig config = getExtension().getForgeUserdevProvider().binaryPatcherConfig;
UserdevConfig.BinaryPatcherConfig config = getExtension().getForgeUserdevProvider().getConfig().binpatcher();
spec.classpath(DependencyDownloader.download(project, config.dependency()));
spec.getMainClass().set("net.minecraftforge.binarypatcher.ConsoleTool");
@@ -565,18 +582,25 @@ public class MinecraftPatchedProvider {
return new McpExecutor(project, minecraftProvider, cache, provider, type.mcpId);
}
public Path getMinecraftSrgJar() {
return minecraftSrgJar;
public Path getMinecraftIntermediateJar() {
return minecraftIntermediateJar;
}
public Path getMinecraftPatchedSrgJar() {
return minecraftPatchedSrgJar;
public Path getMinecraftPatchedIntermediateJar() {
return minecraftPatchedIntermediateJar;
}
public Path getMinecraftPatchedJar() {
return minecraftPatchedJar;
}
/**
* Checks whether the provider's state is dirty (regenerating jars).
*/
public boolean isDirty() {
return dirty;
}
public enum Type {
CLIENT_ONLY("client", "client", (patch, userdev) -> patch.clientPatches),
SERVER_ONLY("server", "server", (patch, userdev) -> patch.serverPatches),

View File

@@ -45,6 +45,7 @@ import org.gradle.api.Project;
import org.gradle.api.logging.LogLevel;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.DependencyInfo;
import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext;
@@ -216,7 +217,8 @@ public class SrgProvider extends DependencyProvider {
if (Files.notExists(mojmapTsrg) || extension.refreshDeps()) {
try (BufferedWriter writer = Files.newBufferedWriter(mojmapTsrg, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
Tsrg2Utils.writeTsrg(visitor -> visitMojmap(visitor, project),
GradleMappingContext context = new GradleMappingContext(project, "tmp-mojmap");
Tsrg2Utils.writeTsrg(visitor -> visitMojangMappings(visitor, context),
MappingsNamespace.NAMED.toString(), false, writer);
}
}
@@ -233,8 +235,9 @@ public class SrgProvider extends DependencyProvider {
if (Files.notExists(mojmapTsrg2) || extension.refreshDeps()) {
try (BufferedWriter writer = Files.newBufferedWriter(mojmapTsrg2, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
GradleMappingContext context = new GradleMappingContext(project, "tmp-mojmap");
MemoryMappingTree tree = new MemoryMappingTree();
visitMojmap(tree, project);
visitMojangMappings(tree, context);
writer.write(Tsrg2Writer.serialize(tree));
}
}
@@ -243,21 +246,12 @@ public class SrgProvider extends DependencyProvider {
return mojmapTsrg2;
}
private static void visitMojmap(MappingVisitor visitor, Project project) {
GradleMappingContext context = new GradleMappingContext(project, "tmp-mojmap");
public static void visitMojangMappings(MappingVisitor visitor, MappingContext context) {
try {
FileUtils.deleteDirectory(context.workingDirectory("/").toFile());
MojangMappingLayer layer = new MojangMappingsSpec(() -> true, true).createLayer(context);
layer.visit(visitor);
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
try {
FileUtils.deleteDirectory(context.workingDirectory("/").toFile());
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
* Copyright (c) 2022-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,12 +24,17 @@
package net.fabricmc.loom.configuration.providers.forge.mcpconfig;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
import net.fabricmc.loom.configuration.providers.forge.mcpconfig.steplogic.StepLogic;
import net.fabricmc.loom.util.function.CollectionUtil;
/**
@@ -38,15 +43,23 @@ import net.fabricmc.loom.util.function.CollectionUtil;
* @param version the Gradle-style dependency string of the program
* @param args the command-line arguments
* @param jvmArgs the JVM arguments
* @param repo the Maven repository to download the dependency from
* @param repo the Maven repository to download the dependency from, or {@code null} if not specified
*/
public record McpConfigFunction(String version, List<ConfigValue> args, List<ConfigValue> jvmArgs, String repo) {
public record McpConfigFunction(String version, List<ConfigValue> args, List<ConfigValue> jvmArgs, @Nullable String repo) {
private static final String VERSION_KEY = "version";
private static final String ARGS_KEY = "args";
private static final String JVM_ARGS_KEY = "jvmargs";
private static final String REPO_KEY = "repo";
public String getDownloadUrl() {
public Path download(StepLogic.ExecutionContext executionContext) throws IOException {
if (repo != null) {
return executionContext.downloadFile(getDownloadUrl());
} else {
return executionContext.downloadDependency(version);
}
}
private String getDownloadUrl() {
String[] parts = version.split(":");
StringBuilder builder = new StringBuilder();
builder.append(repo);
@@ -72,7 +85,8 @@ public record McpConfigFunction(String version, List<ConfigValue> args, List<Con
String version = json.get(VERSION_KEY).getAsString();
List<ConfigValue> args = json.has(ARGS_KEY) ? configValuesFromJson(json.getAsJsonArray(ARGS_KEY)) : List.of();
List<ConfigValue> jvmArgs = json.has(JVM_ARGS_KEY) ? configValuesFromJson(json.getAsJsonArray(JVM_ARGS_KEY)) : List.of();
String repo = json.get(REPO_KEY).getAsString();
JsonElement repoJson = json.get(REPO_KEY);
@Nullable String repo = repoJson.isJsonPrimitive() ? repoJson.getAsString() : null;
return new McpConfigFunction(version, args, jvmArgs, repo);
}

View File

@@ -77,7 +77,8 @@ public class McpConfigProvider extends DependencyProvider {
}
private void init(String version) {
Path dir = getMinecraftProvider().dir("mcp/" + version).toPath();
String mcpName = getExtension().isNeoForge() ? "neoform" : "mcp";
Path dir = getMinecraftProvider().dir(mcpName + "/" + version).toPath();
mcp = dir.resolve("mcp.zip");
unpacked = dir.resolve("unpacked");
configJson = unpacked.resolve("config.json");

View File

@@ -46,6 +46,8 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.logging.Logger;
import org.gradle.process.JavaExecSpec;
@@ -298,12 +300,20 @@ public final class McpExecutor {
}
@Override
public Path download(String url) throws IOException {
public Path downloadFile(String url) throws IOException {
Path path = getDownloadCache().resolve(Hashing.sha256().hashString(url, StandardCharsets.UTF_8).toString().substring(0, 24));
redirectAwareDownload(url, path);
return path;
}
@Override
public Path downloadDependency(String notation) {
final Dependency dependency = project.getDependencies().create(notation);
final Configuration configuration = project.getConfigurations().detachedConfiguration(dependency);
configuration.setTransitive(false);
return configuration.getSingleFile().toPath();
}
@Override
public DownloadBuilder downloadBuilder(String url) {
return LoomGradleExtension.get(project).download(url);

View File

@@ -47,7 +47,7 @@ public final class FunctionLogic implements StepLogic {
// The other tools seem to work with the name containing .jar anyway.
// Technically, FG supports an "outputExtension" config value for steps, but it's not used in practice.
context.setOutput("output.jar");
Path jar = context.download(function.getDownloadUrl());
Path jar = function.download(context);
String mainClass;
try (JarFile jarFile = new JarFile(jar.toFile())) {

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
* Copyright (c) 2022-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -61,7 +61,8 @@ public interface StepLogic {
/** Mappings extracted from {@code data.mappings} in the MCPConfig JSON. */
Path mappings();
String resolve(ConfigValue value);
Path download(String url) throws IOException;
Path downloadFile(String url) throws IOException;
Path downloadDependency(String notation);
DownloadBuilder downloadBuilder(String url);
void javaexec(Action<? super JavaExecSpec> configurator);
Set<File> getMinecraftLibraries();

View File

@@ -38,14 +38,14 @@ public interface ForgeMinecraftProvider {
MinecraftPatchedProvider getPatchedProvider();
static MergedMinecraftProvider createMerged(ConfigContext context) {
return LoomGradleExtension.get(context.project()).isForge() ? new MergedForgeMinecraftProvider(context) : new MergedMinecraftProvider(context);
return LoomGradleExtension.get(context.project()).isForgeLike() ? new MergedForgeMinecraftProvider(context) : new MergedMinecraftProvider(context);
}
static SingleJarMinecraftProvider createServerOnly(ConfigContext context) {
return LoomGradleExtension.get(context.project()).isForge() ? SingleJarForgeMinecraftProvider.server(context) : SingleJarMinecraftProvider.server(context);
return LoomGradleExtension.get(context.project()).isForgeLike() ? SingleJarForgeMinecraftProvider.server(context) : SingleJarMinecraftProvider.server(context);
}
static SingleJarMinecraftProvider createClientOnly(ConfigContext context) {
return LoomGradleExtension.get(context.project()).isForge() ? SingleJarForgeMinecraftProvider.client(context) : SingleJarMinecraftProvider.client(context);
return LoomGradleExtension.get(context.project()).isForgeLike() ? SingleJarForgeMinecraftProvider.client(context) : SingleJarMinecraftProvider.client(context);
}
}

View File

@@ -44,6 +44,7 @@ import java.util.Objects;
import com.google.common.base.Stopwatch;
import com.google.gson.JsonObject;
import dev.architectury.loom.util.MappingOption;
import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
@@ -54,6 +55,7 @@ import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.configuration.DependencyInfo;
import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingConfiguration;
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
@@ -67,10 +69,11 @@ import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.service.ScopedSharedServiceManager;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.loom.util.srg.MCPReader;
import net.fabricmc.loom.util.srg.SrgMerger;
import net.fabricmc.loom.util.srg.ForgeMappingsMerger;
import net.fabricmc.loom.util.srg.SrgNamedWriter;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.stitch.Command;
import net.fabricmc.stitch.commands.CommandProposeFieldNames;
import net.fabricmc.stitch.commands.tinyv2.TinyFile;
@@ -87,6 +90,7 @@ public class MappingConfiguration {
// The mappings we use in practice
public Path tinyMappings;
public final Path tinyMappingsJar;
public Path tinyMappingsWithMojang;
public Path tinyMappingsWithSrg;
public final Map<String, Path> mixinTinyMappings; // The mixin mappings have other names in intermediary.
public final Path srgToNamedSrg; // FORGE: srg to named in srg file format
@@ -105,6 +109,7 @@ public class MappingConfiguration {
this.tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar");
this.unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick");
this.tinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-srg.tiny");
this.tinyMappingsWithMojang = mappingsWorkingDir.resolve("mappings-mojang.tiny");
this.mixinTinyMappings = new HashMap<>();
this.srgToNamedSrg = mappingsWorkingDir.resolve("mappings-srg-named.srg");
}
@@ -124,7 +129,7 @@ public class MappingConfiguration {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
String mappingsIdentifier;
if (extension.isForge()) {
if (extension.isForgeLike()) {
mappingsIdentifier = FieldMigratedMappingConfiguration.createForgeMappingsIdentifier(extension, mappingsName, version, getMappingsClassifier(dependency, jarInfo.v2()), minecraftProvider.minecraftVersion());
} else {
mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, jarInfo.v2()), minecraftProvider.minecraftVersion());
@@ -138,7 +143,7 @@ public class MappingConfiguration {
MappingConfiguration mappingConfiguration;
if (extension.isForge()) {
if (extension.isForgeLike()) {
mappingConfiguration = new FieldMigratedMappingConfiguration(mappingsIdentifier, workingDir);
} else {
mappingConfiguration = new MappingConfiguration(mappingsIdentifier, workingDir);
@@ -155,21 +160,27 @@ public class MappingConfiguration {
}
public TinyMappingsService getMappingsService(SharedServiceManager serviceManager) {
return getMappingsService(serviceManager, false);
return getMappingsService(serviceManager, MappingOption.DEFAULT);
}
public TinyMappingsService getMappingsService(SharedServiceManager serviceManager, boolean withSrg) {
final Path tinyMappings;
if (withSrg) {
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!");
}
tinyMappings = this.tinyMappingsWithSrg;
} else {
tinyMappings = this.tinyMappings;
yield this.tinyMappingsWithSrg;
}
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));
}
@@ -196,12 +207,31 @@ public class MappingConfiguration {
public void setupPost(Project project) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.isNeoForge()) {
// Generate the Mojmap-merged mappings if needed.
// Note that this needs to happen before manipulateMappings for FieldMigratedMappingConfiguration.
if (Files.notExists(tinyMappingsWithMojang) || extension.refreshDeps()) {
final Stopwatch stopwatch = Stopwatch.createStarted();
final MappingContext context = new GradleMappingContext(project, "tmp-neoforge");
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(tinyMappingsWithMojang), false)) {
ForgeMappingsMerger.mergeMojang(context, tinyMappings, null, true).accept(writer);
}
project.getLogger().info(":merged mojang mappings in {}", stopwatch.stop());
}
}
if (extension.shouldGenerateSrgTiny()) {
if (Files.notExists(tinyMappingsWithSrg) || extension.refreshDeps()) {
// Merge tiny mappings with srg
Stopwatch stopwatch = Stopwatch.createStarted();
SrgMerger.ExtraMappings extraMappings = SrgMerger.ExtraMappings.ofMojmapTsrg(getMojmapSrgFileIfPossible(project));
SrgMerger.mergeSrg(getRawSrgFile(project), tinyMappings, tinyMappingsWithSrg, extraMappings, true);
ForgeMappingsMerger.ExtraMappings extraMappings = ForgeMappingsMerger.ExtraMappings.ofMojmapTsrg(getMojmapSrgFileIfPossible(project));
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(tinyMappingsWithSrg), false)) {
ForgeMappingsMerger.mergeSrg(getRawSrgFile(project), tinyMappings, extraMappings, true).accept(writer);
}
project.getLogger().info(":merged srg mappings in " + stopwatch.stop());
}
}
@@ -230,7 +260,7 @@ public class MappingConfiguration {
if (Files.notExists(srgToNamedSrg) || extension.refreshDeps()) {
try (var serviceManager = new ScopedSharedServiceManager()) {
TinyMappingsService mappingsService = getMappingsService(serviceManager, true);
TinyMappingsService mappingsService = getMappingsService(serviceManager, MappingOption.WITH_SRG);
SrgNamedWriter.writeTo(project.getLogger(), srgToNamedSrg, mappingsService.getMappingTree(), "srg", "named");
}
}
@@ -300,7 +330,7 @@ public class MappingConfiguration {
MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediateMappingsService);
} else {
if (LoomGradleExtension.get(project).isForge()) {
if (LoomGradleExtension.get(project).isForgeLike()) {
// (2022-09-11) This is due to ordering issues.
// To complete V1 mappings, we need the full MC jar.
// On Forge, producing the full MC jar needs the list of all Forge dependencies
@@ -507,14 +537,14 @@ public class MappingConfiguration {
}
public Path getReplacedTarget(LoomGradleExtension loom, String namespace) {
if (namespace.equals("intermediary")) return loom.shouldGenerateSrgTiny() ? tinyMappingsWithSrg : tinyMappings;
if (namespace.equals("intermediary")) return getPlatformMappingFile(loom);
return mixinTinyMappings.computeIfAbsent(namespace, k -> {
Path path = mappingsWorkingDir.resolve("mappings-mixin-" + namespace + ".tiny");
try {
if (Files.notExists(path) || loom.refreshDeps()) {
List<String> lines = new ArrayList<>(Files.readAllLines(loom.shouldGenerateSrgTiny() ? tinyMappingsWithSrg : tinyMappings));
List<String> lines = new ArrayList<>(Files.readAllLines(getPlatformMappingFile(loom)));
lines.set(0, lines.get(0).replace("intermediary", "yraidemretni").replace(namespace, "intermediary"));
Files.deleteIfExists(path);
Files.write(path, lines);
@@ -527,6 +557,22 @@ public class MappingConfiguration {
});
}
/**
* The mapping file that is specific to the platform settings.
* It contains SRG (Forge/common) or Mojang mappings (NeoForge) as needed.
*
* @return the platform mapping file path
*/
public Path getPlatformMappingFile(LoomGradleExtension extension) {
if (extension.shouldGenerateSrgTiny()) {
return tinyMappingsWithSrg;
} else if (extension.isNeoForge()) {
return tinyMappingsWithMojang;
} else {
return tinyMappings;
}
}
public record UnpickMetadata(String unpickGroup, String unpickVersion) {
}
}

View File

@@ -38,6 +38,7 @@ import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager;
import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MojangMappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.ProcessedNamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
@@ -48,6 +49,7 @@ public enum MinecraftJarConfiguration {
IntermediaryMinecraftProvider.MergedImpl::new,
NamedMinecraftProvider.MergedImpl::new,
SrgMinecraftProvider.MergedImpl::new,
MojangMappedMinecraftProvider.MergedImpl::new,
ProcessedNamedMinecraftProvider.MergedImpl::new,
SingleJarDecompileConfiguration::new,
List.of("client", "server")
@@ -57,6 +59,7 @@ public enum MinecraftJarConfiguration {
IntermediaryMinecraftProvider.SingleJarImpl::server,
NamedMinecraftProvider.SingleJarImpl::server,
SrgMinecraftProvider.SingleJarImpl::server,
MojangMappedMinecraftProvider.SingleJarImpl::server,
ProcessedNamedMinecraftProvider.SingleJarImpl::server,
SingleJarDecompileConfiguration::new,
List.of("server")
@@ -66,6 +69,7 @@ public enum MinecraftJarConfiguration {
IntermediaryMinecraftProvider.SingleJarImpl::client,
NamedMinecraftProvider.SingleJarImpl::client,
SrgMinecraftProvider.SingleJarImpl::client,
MojangMappedMinecraftProvider.SingleJarImpl::client,
ProcessedNamedMinecraftProvider.SingleJarImpl::client,
SingleJarDecompileConfiguration::new,
List.of("client")
@@ -75,6 +79,7 @@ public enum MinecraftJarConfiguration {
IntermediaryMinecraftProvider.SplitImpl::new,
NamedMinecraftProvider.SplitImpl::new,
SrgMinecraftProvider.SplitImpl::new,
MojangMappedMinecraftProvider.SplitImpl::new,
ProcessedNamedMinecraftProvider.SplitImpl::new,
SplitDecompileConfiguration::new,
List.of("client", "server")
@@ -84,6 +89,7 @@ public enum MinecraftJarConfiguration {
private final BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>> intermediaryMinecraftProviderBiFunction;
private final BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>> namedMinecraftProviderBiFunction;
private final BiFunction<Project, MinecraftProvider, SrgMinecraftProvider<?>> srgMinecraftProviderBiFunction;
private final BiFunction<Project, MinecraftProvider, MojangMappedMinecraftProvider<?>> mojangMappedMinecraftProviderBiFunction;
private final BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>> processedNamedMinecraftProviderBiFunction;
private final BiFunction<ConfigContext, MappedMinecraftProvider, DecompileConfiguration<?>> decompileConfigurationBiFunction;
private final List<String> supportedEnvironments;
@@ -94,6 +100,7 @@ public enum MinecraftJarConfiguration {
BiFunction<Project, M, IntermediaryMinecraftProvider<M>> intermediaryMinecraftProviderBiFunction,
BiFunction<Project, M, P> namedMinecraftProviderBiFunction,
BiFunction<Project, M, SrgMinecraftProvider<M>> srgMinecraftProviderBiFunction,
BiFunction<Project, M, MojangMappedMinecraftProvider<M>> mojangMappedMinecraftProviderBiFunction,
BiFunction<P, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<M, P>> processedNamedMinecraftProviderBiFunction,
BiFunction<ConfigContext, Q, DecompileConfiguration<?>> decompileConfigurationBiFunction,
List<String> supportedEnvironments
@@ -102,6 +109,7 @@ public enum MinecraftJarConfiguration {
this.intermediaryMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, IntermediaryMinecraftProvider<?>>) (Object) intermediaryMinecraftProviderBiFunction;
this.namedMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, NamedMinecraftProvider<?>>) namedMinecraftProviderBiFunction;
this.srgMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, SrgMinecraftProvider<?>>) (Object) srgMinecraftProviderBiFunction;
this.mojangMappedMinecraftProviderBiFunction = (BiFunction<Project, MinecraftProvider, MojangMappedMinecraftProvider<?>>) (Object) mojangMappedMinecraftProviderBiFunction;
this.processedNamedMinecraftProviderBiFunction = (BiFunction<NamedMinecraftProvider<?>, MinecraftJarProcessorManager, ProcessedNamedMinecraftProvider<?, ?>>) (Object) processedNamedMinecraftProviderBiFunction;
this.decompileConfigurationBiFunction = (BiFunction<ConfigContext, MappedMinecraftProvider, DecompileConfiguration<?>>) decompileConfigurationBiFunction;
this.supportedEnvironments = supportedEnvironments;
@@ -131,6 +139,10 @@ public enum MinecraftJarConfiguration {
return srgMinecraftProviderBiFunction;
}
public BiFunction<Project, MinecraftProvider, MojangMappedMinecraftProvider<?>> getMojangMappedMinecraftProviderBiFunction() {
return mojangMappedMinecraftProviderBiFunction;
}
public List<String> getSupportedEnvironments() {
return supportedEnvironments;
}

View File

@@ -76,7 +76,7 @@ public abstract class MinecraftProvider {
final DependencyInfo dependency = DependencyInfo.create(getProject(), Constants.Configurations.MINECRAFT);
minecraftVersion = dependency.getDependency().getVersion();
if (getExtension().shouldGenerateSrgTiny() && !getExtension().isForge()) {
if (getExtension().shouldGenerateSrgTiny() && !getExtension().isForgeLike()) {
getProject().getDependencies().add(Constants.Configurations.SRG, "de.oceanlabs.mcp:mcp_config:" + minecraftVersion);
}

View File

@@ -70,7 +70,7 @@ public abstract sealed class MinecraftSourceSets permits MinecraftSourceSets.Sin
configuration.extendsFrom(configurations.getByName(Constants.Configurations.LOADER_DEPENDENCIES));
configuration.extendsFrom(configurations.getByName(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES));
if (LoomGradleExtension.get(project).isForge()) {
if (LoomGradleExtension.get(project).isForgeLike()) {
configurations.getByName(Constants.Configurations.FORGE_RUNTIME_LIBRARY).extendsFrom(configuration);
}
});

View File

@@ -36,14 +36,17 @@ import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Function;
import dev.architectury.loom.util.MappingOption;
import dev.architectury.tinyremapper.OutputConsumerPath;
import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.build.IntermediaryNamespaces;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper;
import net.fabricmc.loom.configuration.providers.forge.minecraft.ForgeMinecraftProvider;
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryMappingsProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.mappings.TinyMappingsService;
@@ -176,6 +179,11 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
}
}
// Architectury: regenerate jars if patches have changed.
if (minecraftProvider instanceof ForgeMinecraftProvider withForge && withForge.getPatchedProvider().isDirty()) {
return false;
}
return true;
}
@@ -194,7 +202,7 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
Files.deleteIfExists(remappedJars.outputJarPath());
final Set<String> classNames = extension.isForge() ? InnerClassRemapper.readClassNames(remappedJars.inputJar()) : Set.of();
final Set<String> classNames = extension.isForgeLike() ? InnerClassRemapper.readClassNames(remappedJars.inputJar()) : Set.of();
final Map<String, String> remappedSignatures = SignatureFixerApplyVisitor.getRemappedSignatures(getTargetNamespace() == MappingsNamespace.INTERMEDIARY, mappingConfiguration, getProject(), configContext.serviceManager(), toM);
TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(getProject(), configContext.serviceManager(), fromM, toM, true, (builder) -> {
builder.extraPostApplyVisitor(new SignatureFixerApplyVisitor(remappedSignatures));
@@ -219,11 +227,21 @@ public abstract class AbstractMappedMinecraftProvider<M extends MinecraftProvide
getMavenHelper(remappedJars.name()).savePom();
if (extension.isForgeAndOfficial()) {
if (extension.isForgeLikeAndOfficial()) {
try (var serviceManager = new ScopedSharedServiceManager()) {
TinyMappingsService mappingsService = extension.getMappingConfiguration().getMappingsService(serviceManager, true);
MemoryMappingTree mappingsWithSrg = mappingsService.getMappingTree();
RemapObjectHolderVisitor.remapObjectHolder(remappedJars.outputJar().getPath(), "net.minecraftforge.registries.ObjectHolderRegistry", mappingsWithSrg, "srg", "named");
final MappingOption mappingOption = MappingOption.forPlatform(extension);
final TinyMappingsService mappingsService = extension.getMappingConfiguration().getMappingsService(serviceManager, mappingOption);
final String className;
if (extension.isNeoForge()) {
className = "net.neoforged.neoforge.registries.ObjectHolderRegistry";
} else {
className = "net.minecraftforge.registries.ObjectHolderRegistry";
}
final String sourceNamespace = IntermediaryNamespaces.intermediary(project);
final MemoryMappingTree mappings = mappingsService.getMappingTree();
RemapObjectHolderVisitor.remapObjectHolder(remappedJars.outputJar().getPath(), className, mappings, sourceNamespace, "named");
}
}
}

View File

@@ -0,0 +1,117 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft.mapped;
import java.util.List;
import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarEnvType;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.SidedClassVisitor;
public abstract sealed class MojangMappedMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits MojangMappedMinecraftProvider.MergedImpl, MojangMappedMinecraftProvider.SingleJarImpl, MojangMappedMinecraftProvider.SplitImpl {
public MojangMappedMinecraftProvider(Project project, M minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public MavenScope getMavenScope() {
return MavenScope.GLOBAL;
}
@Override
public final MappingsNamespace getTargetNamespace() {
return MappingsNamespace.MOJANG;
}
public static final class MergedImpl extends MojangMappedMinecraftProvider<MergedMinecraftProvider> implements Merged {
public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), MappingsNamespace.OFFICIAL)
);
}
}
public static final class SplitImpl extends MojangMappedMinecraftProvider<SplitMinecraftProvider> implements Split {
public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) {
super(project, minecraftProvider);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), MappingsNamespace.OFFICIAL),
new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar())
);
}
@Override
protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) {
if (remappedJars.outputJar().equals(getClientOnlyJar())) {
tinyRemapperBuilder.extraPostApplyVisitor(SidedClassVisitor.CLIENT);
}
}
}
public static final class SingleJarImpl extends MojangMappedMinecraftProvider<SingleJarMinecraftProvider> implements SingleJar {
private final SingleJarEnvType env;
private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, SingleJarEnvType env) {
super(project, minecraftProvider);
this.env = env;
}
public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, SingleJarEnvType.SERVER);
}
public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, SingleJarEnvType.CLIENT);
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL)
);
}
@Override
public SingleJarEnvType env() {
return env;
}
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021-2022 FabricMC
* Copyright (c) 2021-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -40,6 +40,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import dev.architectury.loom.util.MappingOption;
import org.apache.commons.io.output.NullOutputStream;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
@@ -48,6 +49,7 @@ import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.build.IntermediaryNamespaces;
import net.fabricmc.loom.configuration.providers.mappings.TinyMappingsService;
import net.fabricmc.loom.task.GenerateSourcesTask;
import net.fabricmc.loom.util.DeletingFileVisitor;
@@ -113,7 +115,7 @@ public class ForgeSourcesRemapper {
public static void provideForgeSources(Project project, SharedServiceManager serviceManager, BiConsumer<String, byte[]> consumer) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
String sourceDependency = extension.getForgeUserdevProvider().getJson().getAsJsonPrimitive("sources").getAsString();
String sourceDependency = extension.getForgeUserdevProvider().getConfig().sources();
List<Path> forgeInstallerSources = new ArrayList<>();
for (File file : DependencyDownloader.download(project, sourceDependency)) {
@@ -202,8 +204,10 @@ public class ForgeSourcesRemapper {
LoomGradleExtension extension = LoomGradleExtension.get(project);
Mercury mercury = SourceRemapper.createMercuryWithClassPath(project, false);
TinyMappingsService mappingsService = extension.getMappingConfiguration().getMappingsService(serviceManager, true);
MappingSet mappings = new TinyMappingsReader(mappingsService.getMappingTree(), "srg", "named").read();
final MappingOption mappingOption = MappingOption.forPlatform(extension);
final String sourceNamespace = IntermediaryNamespaces.intermediary(project);
TinyMappingsService mappingsService = extension.getMappingConfiguration().getMappingsService(serviceManager, mappingOption);
MappingSet mappings = new TinyMappingsReader(mappingsService.getMappingTree(), sourceNamespace, "named").read();
for (Map.Entry<String, String> entry : TinyRemapperHelper.JSR_TO_JETBRAINS.entrySet()) {
mappings.getOrCreateClassMapping(entry.getKey()).setDeobfuscatedName(entry.getValue());
@@ -217,8 +221,9 @@ public class ForgeSourcesRemapper {
mercury.getClassPath().add(file.toPath());
}
// Distinct and add the srg jar at the top, so it gets prioritized
mercury.getClassPath().addAll(0, extension.getMinecraftJars(MappingsNamespace.SRG));
// Distinct and add the srg/mojang jar at the top, so it gets prioritized
MappingsNamespace sourceNs = extension.isNeoForge() ? MappingsNamespace.MOJANG : MappingsNamespace.SRG;
mercury.getClassPath().addAll(0, extension.getMinecraftJars(sourceNs));
List<Path> newClassPath = mercury.getClassPath().stream()
.distinct()

View File

@@ -54,6 +54,7 @@ import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.MixinExtensionAPI;
import net.fabricmc.loom.api.ModSettings;
import net.fabricmc.loom.api.NeoForgeExtensionAPI;
import net.fabricmc.loom.api.RemapConfigurationSettings;
import net.fabricmc.loom.api.decompilers.DecompilerOptions;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
@@ -134,7 +135,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
.convention(true);
this.transitiveAccessWideners.finalizeValueOnRead();
this.modProvidedJavadoc = project.getObjects().property(Boolean.class)
.convention(project.provider(() -> !isForge()));
.convention(project.provider(() -> !isForgeLike()));
this.modProvidedJavadoc.finalizeValueOnRead();
this.intermediary = project.getObjects().property(String.class)
.convention("https://maven.fabricmc.net/net/fabricmc/intermediary/%1$s/intermediary-%1$s-v2.jar");
@@ -446,6 +447,14 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
@Override
public void setGenerateSrgTiny(Boolean generateSrgTiny) {
if (isNeoForge()) {
// This is unsupported because supporting the full 2x2 combination of
// [no extra NS] [SRG]
// [mojang] [SRG+mojang]
// is a bit verbose to support.
throw new UnsupportedOperationException("SRG is not supported on NeoForge.");
}
this.generateSrgTiny = generateSrgTiny;
}
@@ -473,6 +482,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
action.execute(getForge());
}
@Override
public void neoForge(Action<NeoForgeExtensionAPI> action) {
action.execute(getNeoForge());
}
// This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods
private final class EnsureCompile extends LoomGradleExtensionApiImpl {
private EnsureCompile() {
@@ -514,5 +528,10 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
public ForgeExtensionAPI getForge() {
throw new RuntimeException("Yeah... something is really wrong");
}
@Override
public NeoForgeExtensionAPI getNeoForge() {
throw new RuntimeException("Yeah... something is really wrong");
}
}
}

View File

@@ -40,6 +40,7 @@ import org.gradle.api.provider.Provider;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.api.NeoForgeExtensionAPI;
import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.InstallerData;
@@ -52,6 +53,7 @@ import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessorManager;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.MojangMappedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.mapped.SrgMinecraftProvider;
import net.fabricmc.loom.util.Constants;
@@ -65,7 +67,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private final MixinExtension mixinApExtension;
private final LoomFiles loomFiles;
private final ConfigurableFileCollection unmappedMods;
private final Supplier<ForgeExtensionAPI> forgeExtension;
private final List<AccessWidenerFile> transitiveAccessWideners = new ArrayList<>();
@@ -75,6 +76,7 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private NamedMinecraftProvider<?> namedMinecraftProvider;
private IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider;
private SrgMinecraftProvider<?> srgMinecraftProvider;
private MojangMappedMinecraftProvider<?> mojangMappedMinecraftProvider;
private InstallerData installerData;
private boolean refreshDeps;
private Provider<Boolean> multiProjectOptimisation;
@@ -85,6 +87,8 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
// +-------------------+
private DependencyProviders dependencyProviders;
private ForgeRunsProvider forgeRunsProvider;
private final Supplier<ForgeExtensionAPI> forgeExtension;
private final Supplier<NeoForgeExtensionAPI> neoForgeExtension;
public LoomGradleExtensionImpl(Project project, LoomFiles files) {
super(project, files);
@@ -94,6 +98,7 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
this.loomFiles = files;
this.unmappedMods = project.files();
this.forgeExtension = Suppliers.memoize(() -> isForge() ? project.getObjects().newInstance(ForgeExtensionImpl.class, project, this) : null);
this.neoForgeExtension = Suppliers.memoize(() -> isNeoForge() ? project.getObjects().newInstance(NeoForgeExtensionImpl.class, project) : null);
// Setup the default intermediate mappings provider.
setIntermediateMappingsProvider(IntermediaryMappingsProvider.class, provider -> {
@@ -185,6 +190,16 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
this.srgMinecraftProvider = srgMinecraftProvider;
}
@Override
public MojangMappedMinecraftProvider<?> getMojangMappedMinecraftProvider() {
return Objects.requireNonNull(mojangMappedMinecraftProvider, "Cannot get MojangMappedMinecraftProvider before it has been setup");
}
@Override
public void setMojangMappedMinecraftProvider(MojangMappedMinecraftProvider<?> mojangMappedMinecraftProvider) {
this.mojangMappedMinecraftProvider = mojangMappedMinecraftProvider;
}
@Override
public FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace) {
return getProject().files(
@@ -293,6 +308,12 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
return forgeExtension.get();
}
@Override
public NeoForgeExtensionAPI getNeoForge() {
ModPlatform.assertPlatform(this, ModPlatform.NEOFORGE);
return neoForgeExtension.get();
}
@Override
public DependencyProviders getDependencyProviders() {
return dependencyProviders;
@@ -305,13 +326,13 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
@Override
public ForgeRunsProvider getForgeRunsProvider() {
ModPlatform.assertPlatform(this, ModPlatform.FORGE);
ModPlatform.assertForgeLike(this);
return forgeRunsProvider;
}
@Override
public void setForgeRunsProvider(ForgeRunsProvider forgeRunsProvider) {
ModPlatform.assertPlatform(this, ModPlatform.FORGE);
ModPlatform.assertForgeLike(this);
this.forgeRunsProvider = forgeRunsProvider;
}
}

View File

@@ -36,6 +36,7 @@ import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.util.PatternSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.MixinExtensionAPI;
import net.fabricmc.loom.build.IntermediaryNamespaces;
@@ -49,8 +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).isForge()));
.convention(true);
.convention(project.provider(() -> !LoomGradleExtension.get(project).isNeoForge()));
this.refmapTargetNamespace = project.getObjects().property(String.class)
.convention(project.provider(() -> IntermediaryNamespaces.intermediary(project)));

View File

@@ -0,0 +1,51 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.extension;
import javax.inject.Inject;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
import net.fabricmc.loom.api.NeoForgeExtensionAPI;
public class NeoForgeExtensionImpl implements NeoForgeExtensionAPI {
private final ConfigurableFileCollection accessTransformers;
@Inject
public NeoForgeExtensionImpl(Project project) {
accessTransformers = project.getObjects().fileCollection();
}
@Override
public ConfigurableFileCollection getAccessTransformers() {
return accessTransformers;
}
@Override
public void accessTransformer(Object file) {
accessTransformers.from(file);
}
}

View File

@@ -37,8 +37,6 @@ import codechicken.diffpatch.cli.PatchOperation;
import codechicken.diffpatch.util.LoggingOutputStream;
import codechicken.diffpatch.util.PatchMode;
import com.google.common.base.Stopwatch;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import dev.architectury.loom.forge.ForgeTools;
import dev.architectury.loom.util.TempFiles;
import org.gradle.api.file.FileCollection;
@@ -62,6 +60,7 @@ import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.service.ScopedSharedServiceManager;
import net.fabricmc.loom.util.service.SharedServiceManager;
// TODO: NeoForge support
public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
/**
* The SRG Minecraft file produced by the MCP executor.
@@ -136,7 +135,7 @@ public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
private Path sourcePatch(Path cache, Path rawDecompiled) throws IOException {
ForgeUserdevProvider userdev = getExtension().getForgeUserdevProvider();
String patchPathInZip = userdev.getJson().getAsJsonPrimitive("patches").getAsString();
String patchPathInZip = userdev.getConfig().patches();
Path output = cache.resolve("patched.jar");
Path rejects = cache.resolve("rejects");
@@ -148,8 +147,8 @@ public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
.outputPath(output)
.mode(PatchMode.ACCESS)
.rejectsPath(rejects)
.aPrefix(userdev.getJson().getAsJsonPrimitive("patchesOriginalPrefix").getAsString())
.bPrefix(userdev.getJson().getAsJsonPrimitive("patchesModifiedPrefix").getAsString())
.aPrefix(userdev.getConfig().patchesOriginalPrefix().orElseThrow())
.bPrefix(userdev.getConfig().patchesModifiedPrefix().orElseThrow())
.build()
.operate();
@@ -173,18 +172,18 @@ public abstract class GenerateForgePatchedSourcesTask extends AbstractLoomTask {
try (var tempFiles = new TempFiles()) {
final ForgeUserdevProvider userdevProvider = getExtension().getForgeUserdevProvider();
final JsonArray sass = userdevProvider.getJson().getAsJsonArray("sass");
final List<String> sass = userdevProvider.getConfig().sass();
final List<Path> sasPaths = new ArrayList<>();
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(userdevProvider.getUserdevJar(), false)) {
for (JsonElement sasPath : sass) {
for (String sasPath : sass) {
try {
final Path from = fs.getPath(sasPath.getAsString());
final Path from = fs.getPath(sasPath);
final Path to = tempFiles.file(null, ".sas");
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
sasPaths.add(to);
} catch (IOException e) {
throw new IOException("Could not extract SAS " + sasPath.getAsString());
throw new IOException("Could not extract SAS " + sasPath);
}
}
}

View File

@@ -192,7 +192,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
// Inject Forge's own sources
if (getExtension().isForge()) {
if (getExtension().isForgeLike()) {
try (var serviceManager = new ScopedSharedServiceManager()) {
ForgeSourcesRemapper.addForgeSources(getProject(), serviceManager, getOutputJar().get().getAsFile().toPath());
}
@@ -296,7 +296,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
params.getClassPath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES));
// Architectury
params.getForge().set(getExtension().isForge());
params.getForge().set(getExtension().isForgeLike());
});
try {
@@ -416,9 +416,12 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
if (Files.exists(linemap)) {
if (getParameters().getForge().get()) {
try {
// Remove Forge classes from linemap
// Remove Forge and NeoForge classes from linemap
// TODO: We should instead not decompile Forge's classes at all
LineMapVisitor.process(linemap, next -> new LineMapClassFilter(next, name -> !name.startsWith("net/minecraftforge/")));
LineMapVisitor.process(linemap, next -> new LineMapClassFilter(next, name -> {
// Skip both Forge and NeoForge classes.
return !name.startsWith("net/minecraftforge/") && !name.startsWith("net/neoforged/");
}));
} catch (IOException e) {
throw new UncheckedIOException("Failed to process linemap", e);
}
@@ -470,7 +473,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
private Path getMappings() {
Path inputMappings = getExtension().isForge() ? getExtension().getMappingConfiguration().tinyMappingsWithSrg : getExtension().getMappingConfiguration().tinyMappings;
Path inputMappings = getExtension().getPlatformMappingFile();
MemoryMappingTree mappingTree = new MemoryMappingTree();

View File

@@ -196,6 +196,10 @@ public abstract class MigrateMappingsTask extends AbstractLoomTask {
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);
}
}
mercury.getProcessors().add(MercuryRemapper.create(mappingSet));

View File

@@ -141,13 +141,13 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
getAddNestedDependencies().convention(true).finalizeValueOnRead();
getReadMixinConfigsFromManifest().convention(LoomGradleExtension.get(getProject()).isForge()).finalizeValueOnRead();
getReadMixinConfigsFromManifest().convention(LoomGradleExtension.get(getProject()).isForgeLike()).finalizeValueOnRead();
getInjectAccessWidener().convention(false);
Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE);
IncludedJarFactory factory = new IncludedJarFactory(getProject());
if (!LoomGradleExtension.get(getProject()).isForge()) {
if (!LoomGradleExtension.get(getProject()).isForgeLike()) {
getNestedJars().from(factory.getNestedJars(includeConfiguration));
} else {
Provider<Pair<List<LazyNestedFile>, TaskDependency>> forgeNestedJars = factory.getForgeNestedJars(includeConfiguration);
@@ -196,7 +196,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
if (getAddNestedDependencies().get()) {
params.getNestedJars().from(getNestedJars());
if (extension.isForge()) {
if (extension.isForgeLike()) {
params.getForgeNestedJars().set(getForgeNestedJars());
}
}
@@ -326,7 +326,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
addNestedJars();
ModBuildExtensions.convertAwToAt(getParameters().getAtAccessWideners(), outputFile, getParameters().getMappingBuildServiceUuid());
if (getParameters().getPlatform().get() != ModPlatform.FORGE) {
if (!getParameters().getPlatform().get().isForgeLike()) {
modifyJarManifest();
}

View File

@@ -40,6 +40,7 @@ import org.apache.commons.io.FileUtils;
import org.gradle.api.logging.configuration.ConsoleOutput;
import org.gradle.api.tasks.TaskAction;
import net.fabricmc.loom.build.IntermediaryNamespaces;
import net.fabricmc.loom.configuration.providers.forge.ConfigValue;
import net.fabricmc.loom.configuration.providers.forge.ForgeRunTemplate;
import net.fabricmc.loom.configuration.providers.forge.ForgeRunsProvider;
@@ -75,7 +76,7 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
.property("client", "org.lwjgl.librarypath", nativesPath);
}
if (!getExtension().isForge()) {
if (!getExtension().isForgeLike()) {
launchConfig
.argument("client", "--assetIndex")
.argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()))
@@ -98,7 +99,7 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
.argument("client", "Architectury Loom");
}
if (getExtension().isForge()) {
if (getExtension().isForgeLike()) {
// Find the mapping files for Unprotect to use for figuring out
// which classes are from Minecraft.
String unprotectMappings = getProject().getConfigurations()
@@ -108,37 +109,46 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask {
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator));
final String intermediateNs = IntermediaryNamespaces.intermediary(getProject());
final String mappingsPath = getExtension().getPlatformMappingFile().toAbsolutePath().toString();
launchConfig
// Should match YarnNamingService.PATH_TO_MAPPINGS in forge-runtime
.property("fabric.yarnWithSrg.path", getExtension().getMappingConfiguration().tinyMappingsWithSrg.toAbsolutePath().toString())
.property("unprotect.mappings", unprotectMappings)
// See ArchitecturyNamingService in forge-runtime
.property("architectury.naming.sourceNamespace", intermediateNs)
.property("architectury.naming.mappingsPath", mappingsPath);
.property("mixin.env.remapRefMap", "true");
if (getExtension().isForge()) {
final List<String> dataGenMods = getExtension().getForge().getDataGenMods();
final List<String> dataGenMods = getExtension().getForge().getDataGenMods();
// Only apply the hardcoded data arguments if the deprecated data generator API is being used.
if (!dataGenMods.isEmpty()) {
launchConfig
.argument("data", "--all")
.argument("data", "--mod")
.argument("data", String.join(",", getExtension().getForge().getDataGenMods()))
.argument("data", "--output")
.argument("data", getProject().file("src/generated/resources").getAbsolutePath());
}
// Only apply the hardcoded data arguments if the deprecated data generator API is being used.
if (!dataGenMods.isEmpty()) {
launchConfig
.argument("data", "--all")
.argument("data", "--mod")
.argument("data", String.join(",", getExtension().getForge().getDataGenMods()))
.argument("data", "--output")
.argument("data", getProject().file("src/generated/resources").getAbsolutePath());
}
launchConfig.property("mixin.env.remapRefMap", "true");
if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) {
launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingConfiguration().getReplacedTarget(getExtension(), "srg").toAbsolutePath().toString());
} else {
launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingConfiguration().srgToNamedSrg.toAbsolutePath().toString());
}
if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) {
// See mixin remapper service in forge-runtime
launchConfig
.property("architectury.mixinRemapper.sourceNamespace", intermediateNs)
.property("architectury.mixinRemapper.mappingsPath", mappingsPath);
} else {
launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingConfiguration().srgToNamedSrg.toAbsolutePath().toString());
}
Set<String> mixinConfigs = PropertyUtil.getAndFinalize(getExtension().getForge().getMixinConfigs());
Set<String> mixinConfigs = PropertyUtil.getAndFinalize(getExtension().getForge().getMixinConfigs());
if (!mixinConfigs.isEmpty()) {
for (String config : mixinConfigs) {
launchConfig.argument("-mixin.config");
launchConfig.argument(config);
if (!mixinConfigs.isEmpty()) {
for (String config : mixinConfigs) {
launchConfig.argument("-mixin.config");
launchConfig.argument(config);
}
}
}

View File

@@ -108,7 +108,7 @@ public abstract class JarManifestService implements BuildService<JarManifestServ
private static Provider<MixinVersion> getMixinVersion(Project project) {
return project.getConfigurations().named(Constants.Configurations.LOADER_DEPENDENCIES).map(configuration -> {
if (LoomGradleExtension.get(project).isForge()) return new MixinVersion("unknown", "unknown");
if (LoomGradleExtension.get(project).isForgeLike()) return new MixinVersion("unknown", "unknown");
// Not super ideal that this uses the mod compile classpath, should prob look into making this not a thing at somepoint
Optional<Dependency> dependency = configuration

View File

@@ -28,6 +28,7 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects;
import dev.architectury.loom.util.MappingOption;
import org.cadixdev.lorenz.MappingSet;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
@@ -46,8 +47,15 @@ public final class LorenzMappingService implements SharedService {
public static synchronized LorenzMappingService create(SharedServiceManager sharedServiceManager, MappingConfiguration mappingConfiguration, MappingsNamespace from, MappingsNamespace to) {
return sharedServiceManager.getOrCreateService(mappingConfiguration.getBuildServiceName("LorenzMappingService", from.toString(), to.toString()), () -> {
boolean srg = (from == MappingsNamespace.SRG || to == MappingsNamespace.SRG);
MemoryMappingTree m = mappingConfiguration.getMappingsService(sharedServiceManager, srg).getMappingTree();
MappingOption mappingOption = MappingOption.DEFAULT;
if (from == MappingsNamespace.SRG || to == MappingsNamespace.SRG) {
mappingOption = MappingOption.WITH_SRG;
} else if (from == MappingsNamespace.MOJANG || to == MappingsNamespace.MOJANG) {
mappingOption = MappingOption.WITH_MOJANG;
}
MemoryMappingTree m = mappingConfiguration.getMappingsService(sharedServiceManager, mappingOption).getMappingTree();
try {
try (var reader = new TinyMappingsReader(m, from.toString(), to.toString())) {

View File

@@ -52,7 +52,7 @@ public final class MappingsService implements SharedService {
final MappingConfiguration mappingConfiguration = LoomGradleExtension.get(project).getMappingConfiguration();
final String name = mappingConfiguration.getBuildServiceName("mappingsProvider", from, to);
return MappingsService.create(serviceManager, name, (from.equals("srg") || to.equals("srg")) && LoomGradleExtension.get(project).shouldGenerateSrgTiny() ? mappingConfiguration.tinyMappingsWithSrg : mappingConfiguration.tinyMappings, from, to, false);
return MappingsService.create(serviceManager, name, LoomGradleExtension.get(project).getPlatformMappingFile(), from, to, false);
}
private final Options options;

View File

@@ -81,8 +81,8 @@ public class TinyRemapperService implements SharedService {
extension.getKnownIndyBsms().get().stream().sorted().forEach(joiner::add);
if (extension.isForge()) {
joiner.add("forge");
if (extension.isForgeLike()) {
joiner.add(extension.getPlatform().get().id());
}
final String id = joiner.toString();

View File

@@ -74,6 +74,7 @@ public class Constants {
public static final String SRG = "srg";
public static final String MCP_CONFIG = "mcp";
public static final String FORGE = "forge";
public static final String NEOFORGE = "neoForge";
public static final String FORGE_USERDEV = "forgeUserdev";
public static final String FORGE_INSTALLER = "forgeInstaller";
public static final String FORGE_UNIVERSAL = "forgeUniversal";

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2021-2022 FabricMC
* Copyright (c) 2021-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -36,7 +36,8 @@ import net.fabricmc.loom.api.LoomGradleExtensionAPI;
public enum ModPlatform {
FABRIC(false),
FORGE(false),
QUILT(true);
QUILT(true),
NEOFORGE(true);
boolean experimental;
@@ -44,10 +45,21 @@ public enum ModPlatform {
this.experimental = experimental;
}
/**
* Returns the lowercase ID of this mod platform.
*/
public String id() {
return name().toLowerCase(Locale.ROOT);
}
public boolean isExperimental() {
return experimental;
}
public boolean isForgeLike() {
return this == FORGE || this == NEOFORGE;
}
public static void assertPlatform(Project project, ModPlatform platform) {
assertPlatform(LoomGradleExtension.get(project), platform);
}
@@ -55,8 +67,8 @@ public enum ModPlatform {
public static void assertPlatform(LoomGradleExtensionAPI extension, ModPlatform platform) {
assertPlatform(extension, platform, () -> {
String msg = "Loom is not running on %s.%nYou can switch to it by adding 'loom.platform = %s' to your gradle.properties";
String name = platform.name().toLowerCase(Locale.ROOT);
return msg.formatted(name, name);
String id = platform.id();
return msg.formatted(id, id);
});
}
@@ -65,4 +77,14 @@ public enum ModPlatform {
throw new GradleException(message.get());
}
}
public static void assertForgeLike(LoomGradleExtensionAPI extension) {
assertForgeLike(extension, () -> "Loom is not running on a Forge-like platform (Forge or NeoForge).");
}
public static void assertForgeLike(LoomGradleExtensionAPI extension, Supplier<String> message) {
if (!extension.getPlatform().get().isForgeLike()) {
throw new GradleException(message.get());
}
}
}

View File

@@ -203,6 +203,10 @@ public class SourceRemapper {
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);
}
}
Set<File> files = project.getConfigurations()

View File

@@ -33,6 +33,7 @@ import java.util.function.Consumer;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableMap;
import dev.architectury.loom.util.MappingOption;
import dev.architectury.tinyremapper.IMappingProvider;
import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
@@ -70,8 +71,8 @@ public final class TinyRemapperHelper {
public static TinyRemapper getTinyRemapper(Project project, SharedServiceManager serviceManager, String fromM, String toM, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer, Set<String> fromClassNames) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
boolean srg = (fromM.equals(MappingsNamespace.SRG.toString()) || toM.equals(MappingsNamespace.SRG.toString())) && extension.isForge();
MemoryMappingTree mappingTree = extension.getMappingConfiguration().getMappingsService(serviceManager, srg).getMappingTree();
final MappingOption mappingOption = MappingOption.forPlatform(extension).forNamespaces(fromM, toM);
MemoryMappingTree mappingTree = extension.getMappingConfiguration().getMappingsService(serviceManager, mappingOption).getMappingTree();
if (fixRecords && !mappingTree.getSrcNamespace().equals(fromM)) {
throw new IllegalStateException("Mappings src namespace must match remap src namespace");
@@ -81,7 +82,7 @@ public final class TinyRemapperHelper {
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
.logUnknownInvokeDynamic(false)
.ignoreConflicts(extension.isForge())
.ignoreConflicts(extension.isForgeLike())
.cacheMappings(true)
.threads(Runtime.getRuntime().availableProcessors())
.logger(project.getLogger()::lifecycle)
@@ -99,7 +100,7 @@ public final class TinyRemapperHelper {
return next;
});
if (extension.isForge()) {
if (extension.isForgeLike()) {
if (!fromClassNames.isEmpty()) {
builder.withMappings(InnerClassRemapper.of(fromClassNames, mappingTree, fromM, toM));
}

View File

@@ -166,7 +166,7 @@ public final class FabricModJsonFactory {
}
public static boolean isModJar(Path input, ModPlatform platform) {
if (platform == ModPlatform.FORGE) {
if (platform.isForgeLike()) {
return ZipUtils.contains(input, "META-INF/mods.toml");
} else if (platform == ModPlatform.QUILT) {
return ZipUtils.contains(input, "quilt.mod.json") || isModJar(input, ModPlatform.FABRIC);
@@ -180,8 +180,8 @@ public final class FabricModJsonFactory {
}
public static boolean isNestableModJar(Path input, ModPlatform platform) {
// Forge don't care if the main jar is mod jar.
if (platform == ModPlatform.FORGE) return true;
// Forge and NeoForge don't care if the main jar is mod jar.
if (platform.isForgeLike()) return true;
return isModJar(input, platform);
}
@@ -190,7 +190,7 @@ public final class FabricModJsonFactory {
return true;
}
if (platform == ModPlatform.FORGE) {
if (platform.isForgeLike()) {
return Files.exists(fs.getPath("META-INF/mods.toml"));
} else if (platform == ModPlatform.QUILT) {
return Files.exists(fs.getPath("quilt.mod.json")) || containsMod(fs, ModPlatform.FABRIC);

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2020-2021 FabricMC
* Copyright (c) 2020-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -34,8 +34,10 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import net.fabricmc.loom.build.IntermediaryNamespaces;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.function.CollectionUtil;
@@ -47,7 +49,10 @@ import net.fabricmc.mappingio.tree.MappingTree;
* @author Juuz
*/
public final class AtRemapper {
public static void remap(Logger logger, Path jar, MappingTree mappings) throws IOException {
public static void remap(Project project, Path jar, MappingTree mappings) throws IOException {
final Logger logger = project.getLogger();
final String sourceNamespace = IntermediaryNamespaces.intermediary(project);
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar, false)) {
Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH);
@@ -76,7 +81,7 @@ public final class AtRemapper {
String name = parts[1].replace('.', '/');
parts[1] = CollectionUtil.find(
mappings.getClasses(),
def -> def.getName("srg").equals(name)
def -> def.getName(sourceNamespace).equals(name)
).map(def -> def.getName("named")).orElse(name).replace('/', '.');
if (parts.length >= 3) {
@@ -84,7 +89,7 @@ public final class AtRemapper {
parts[2] = parts[2].substring(0, parts[2].indexOf('(')) + remapDescriptor(parts[2].substring(parts[2].indexOf('(')), s -> {
return CollectionUtil.find(
mappings.getClasses(),
def -> def.getName("srg").equals(s)
def -> def.getName(sourceNamespace).equals(s)
).map(def -> def.getName("named")).orElse(s);
});
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2020-2021 FabricMC
* Copyright (c) 2020-2023 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -34,15 +34,19 @@ import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import net.fabricmc.loom.build.IntermediaryNamespaces;
import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.ModPlatform;
import net.fabricmc.loom.util.function.CollectionUtil;
import net.fabricmc.mappingio.tree.MappingTree;
@@ -53,8 +57,12 @@ import net.fabricmc.mappingio.tree.MappingTree;
*/
public final class CoreModClassRemapper {
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^(.*')((?:com\\.mojang\\.|net\\.minecraft\\.)[A-Za-z0-9.-_$]+)('.*)$");
private static final Pattern REDIRECT_FIELD_TO_METHOD_PATTERN = Pattern.compile("^(.*\\w+\\s*\\.\\s*redirectFieldToMethod\\s*\\(\\s*\\w+\\s*,\\s*')(\\w*)('\\s*,(?:\\s*'(\\w+)'\\s*|.*)\\).*)$");
public static void remapJar(Project project, ModPlatform platform, Path jar, MappingTree mappings) throws IOException {
final Logger logger = project.getLogger();
final String sourceNamespace = IntermediaryNamespaces.intermediary(project);
public static void remapJar(Path jar, MappingTree mappings, Logger logger) throws IOException {
try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar, false)) {
Path coremodsJsonPath = fs.getPath("META-INF", "coremods.json");
@@ -75,7 +83,7 @@ public final class CoreModClassRemapper {
if (Files.exists(js)) {
logger.info(":remapping coremod '" + file + "'");
remap(js, mappings);
remap(js, platform, mappings, sourceNamespace);
} else {
logger.warn("Coremod '" + file + "' listed in coremods.json but not found");
}
@@ -83,9 +91,10 @@ public final class CoreModClassRemapper {
}
}
public static void remap(Path js, MappingTree mappings) throws IOException {
public static void remap(Path js, ModPlatform platform, MappingTree mappings, String sourceNamespace) throws IOException {
List<String> lines = Files.readAllLines(js);
List<String> output = new ArrayList<>(lines);
String lastClassName = null;
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
@@ -93,13 +102,39 @@ public final class CoreModClassRemapper {
if (matcher.matches()) {
String className = matcher.group(2).replace('.', '/');
String remapped = CollectionUtil.find(mappings.getClasses(), def -> def.getName("srg").equals(className))
String remapped = CollectionUtil.find(mappings.getClasses(), def -> def.getName(sourceNamespace).equals(className))
.map(def -> def.getName("named"))
.orElse(className);
lastClassName = remapped;
if (!className.equals(remapped)) {
output.set(i, matcher.group(1) + remapped.replace('/', '.') + matcher.group(3));
}
} else if (platform == ModPlatform.NEOFORGE && lastClassName != null) {
matcher = REDIRECT_FIELD_TO_METHOD_PATTERN.matcher(line);
if (matcher.matches()) {
String fieldName = matcher.group(2);
String remapped = Optional.ofNullable(mappings.getClass(lastClassName, mappings.getNamespaceId("named")))
.flatMap(clazz -> Optional.ofNullable(clazz.getField(fieldName, null, mappings.getNamespaceId(sourceNamespace))))
.map(field -> field.getName("named"))
.orElse(fieldName);
if (!fieldName.equals(remapped)) {
String optionalMethod = matcher.group(4);
String remappedMethod = optionalMethod == null ? null
: Optional.ofNullable(mappings.getClass(lastClassName, mappings.getNamespaceId("named")))
.flatMap(clazz -> Optional.ofNullable(clazz.getMethod(optionalMethod, null, mappings.getNamespaceId(sourceNamespace))))
.map(method -> method.getName("named"))
.orElse(null);
if (remappedMethod != null) {
output.set(i, matcher.group(1) + remapped + matcher.group(3).replace("'" + optionalMethod + "'", "'" + remappedMethod + "'"));
} else {
output.set(i, matcher.group(1) + remapped + matcher.group(3));
}
}
}
}
}

View File

@@ -34,56 +34,57 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.util.MappingException;
import net.fabricmc.loom.util.function.CollectionUtil;
import net.fabricmc.mappingio.FlatMappingVisitor;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.adapter.ForwardingMappingVisitor;
import net.fabricmc.mappingio.adapter.MappingNsRenamer;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.format.TsrgReader;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MappingTreeView;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
/**
* Merges a Tiny file with an SRG file.
* Merges a Tiny file with a new namespace.
*
* @author Juuz
*/
public final class SrgMerger {
public final class ForgeMappingsMerger {
private static final List<String> INPUT_NAMESPACES = List.of("official", "intermediary", "named");
private final MemoryMappingTree srg;
private final MemoryMappingTree newNs;
private final MemoryMappingTree src;
private final MemoryMappingTree output;
private final FlatMappingVisitor flatOutput;
private final boolean lenient;
private final @Nullable MemoryMappingTree extra;
private final ListMultimap<SrgMethodKey, MethodData> methodsBySrgName;
private final ListMultimap<MethodKey, MethodData> methodsByNewNs;
private SrgMerger(Path srg, Path tiny, @Nullable ExtraMappings extraMappings, boolean lenient) throws IOException {
this.srg = readSrg(srg);
this.src = new MemoryMappingTree();
private ForgeMappingsMerger(MemoryMappingTree newNs, MemoryMappingTree src, @Nullable ExtraMappings extraMappings, boolean lenient) throws IOException {
this.newNs = newNs;
Preconditions.checkArgument(this.newNs.getDstNamespaces().size() == 1, "New namespace must have exactly one destination namespace");
this.src = src;
this.output = new MemoryMappingTree();
this.flatOutput = new RegularAsFlatMappingVisitor(output);
this.lenient = lenient;
this.methodsBySrgName = ArrayListMultimap.create();
this.methodsByNewNs = ArrayListMultimap.create();
if (extraMappings != null) {
this.extra = new MemoryMappingTree();
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(extra, "official");
MappingVisitor visitor = nsSwitch;
MappingVisitor visitor = new MappingSourceNsSwitch(this.extra, MappingsNamespace.OFFICIAL.toString());
if (!extraMappings.hasCorrectNamespaces()) {
Map<String, String> namespaces = Map.of(
@@ -98,33 +99,37 @@ public final class SrgMerger {
this.extra = null;
}
MappingReader.read(tiny, this.src);
checkInputNamespaces(tiny);
this.output.visitNamespaces(this.src.getSrcNamespace(), Stream.concat(Stream.of("srg"), this.src.getDstNamespaces().stream()).collect(Collectors.toList()));
var newDstNamespaces = new ArrayList<String>();
newDstNamespaces.add(this.newNs.getDstNamespaces().get(0));
newDstNamespaces.addAll(this.src.getDstNamespaces());
this.output.visitNamespaces(this.src.getSrcNamespace(), newDstNamespaces);
}
private void checkInputNamespaces(Path tiny) {
List<String> inputNamespaces = new ArrayList<>(this.src.getDstNamespaces());
inputNamespaces.add(0, this.src.getSrcNamespace());
private static MemoryMappingTree readInput(Path tiny) throws IOException {
MemoryMappingTree src = new MemoryMappingTree();
MappingReader.read(tiny, src);
List<String> inputNamespaces = new ArrayList<>(src.getDstNamespaces());
inputNamespaces.add(0, src.getSrcNamespace());
if (!inputNamespaces.equals(INPUT_NAMESPACES)) {
throw new MappingException("Mapping file " + tiny + " does not have 'official, intermediary, named' as its namespaces! Found: " + inputNamespaces);
}
return src;
}
/**
* Creates a destination name array where the first element
* will be the srg name of the mapping element.
* will be the new namespace name of the mapping element.
*/
private String[] createDstNameArray(MappingTree.ElementMappingView srg) {
private String[] createDstNameArray(MappingTree.ElementMappingView newNs) {
String[] dstNames = new String[output.getDstNamespaces().size()];
dstNames[0] = srg.getDstName(0);
dstNames[0] = newNs.getDstName(0);
return dstNames;
}
/**
* Copies all the non-srg destination names from an element.
* Copies all the original destination names from an element.
*/
private void copyDstNames(String[] dstNames, MappingTreeView.ElementMappingView from) {
for (int i = 1; i < dstNames.length; i++) {
@@ -135,37 +140,37 @@ public final class SrgMerger {
/**
* Fills in an array of destination names with the element's source name.
*/
private void fillMappings(String[] names, MappingTree.ElementMappingView srg) {
private void fillMappings(String[] names, MappingTree.ElementMappingView newNs) {
for (int i = 1; i < names.length; i++) {
names[i] = srg.getSrcName();
names[i] = newNs.getSrcName();
}
}
public MemoryMappingTree merge() throws IOException {
for (MappingTree.ClassMapping srgClass : srg.getClasses()) {
String[] dstNames = createDstNameArray(srgClass);
MappingTree.ClassMapping tinyClass = src.getClass(srgClass.getSrcName());
for (MappingTree.ClassMapping newNsClass : newNs.getClasses()) {
String[] dstNames = createDstNameArray(newNsClass);
MappingTree.ClassMapping tinyClass = src.getClass(newNsClass.getSrcName());
String comment = null;
if (tinyClass != null) {
copyDstNames(dstNames, tinyClass);
comment = tinyClass.getComment();
} else if (lenient) {
// Tiny class not found, we'll just use srg names
fillMappings(dstNames, srgClass);
// Tiny class not found, we'll just use new namespace names
fillMappings(dstNames, newNsClass);
} else {
throw new MappingException("Could not find class " + srgClass.getSrcName() + "|" + srgClass.getDstName(0));
throw new MappingException("Could not find class " + newNsClass.getSrcName() + "|" + newNsClass.getDstName(0));
}
flatOutput.visitClass(srgClass.getSrcName(), dstNames);
if (comment != null) flatOutput.visitClassComment(srgClass.getSrcName(), comment);
flatOutput.visitClass(newNsClass.getSrcName(), dstNames);
if (comment != null) flatOutput.visitClassComment(newNsClass.getSrcName(), comment);
for (MappingTree.FieldMapping field : srgClass.getFields()) {
mergeField(srgClass, field, tinyClass);
for (MappingTree.FieldMapping field : newNsClass.getFields()) {
mergeField(newNsClass, field, tinyClass);
}
for (MappingTree.MethodMapping method : srgClass.getMethods()) {
mergeMethod(srgClass, method, tinyClass);
for (MappingTree.MethodMapping method : newNsClass.getMethods()) {
mergeMethod(newNsClass, method, tinyClass);
}
}
@@ -174,20 +179,20 @@ public final class SrgMerger {
return output;
}
private void mergeField(MappingTree.ClassMapping srgClass, MappingTree.FieldMapping srgField, @Nullable MappingTree.ClassMapping tinyClass) throws IOException {
String[] dstNames = createDstNameArray(srgField);
private void mergeField(MappingTree.ClassMapping newNsClass, MappingTree.FieldMapping newNsField, @Nullable MappingTree.ClassMapping tinyClass) throws IOException {
String[] dstNames = createDstNameArray(newNsField);
MappingTree.FieldMapping tinyField = null;
String srcDesc = srgField.getSrcDesc();
String srcDesc = newNsField.getSrcDesc();
String comment = null;
if (tinyClass != null) {
if (srcDesc != null) {
tinyField = tinyClass.getField(srgField.getSrcName(), srgField.getSrcDesc());
tinyField = tinyClass.getField(newNsField.getSrcName(), newNsField.getSrcDesc());
} else {
tinyField = CollectionUtil.find(tinyClass.getFields(), field -> field.getSrcName().equals(srgField.getSrcName())).orElse(null);
tinyField = CollectionUtil.find(tinyClass.getFields(), field -> field.getSrcName().equals(newNsField.getSrcName())).orElse(null);
}
} else if (!lenient) {
throw new MappingException("Could not find field " + srgClass.getDstName(0) + '.' + srgField.getDstName(0) + ' ' + srgField.getDstDesc(0));
throw new MappingException("Could not find field " + newNsClass.getDstName(0) + '.' + newNsField.getDstName(0) + ' ' + newNsField.getDstDesc(0));
}
if (tinyField != null) {
@@ -195,27 +200,27 @@ public final class SrgMerger {
srcDesc = tinyField.getSrcDesc();
comment = tinyField.getComment();
} else {
fillMappings(dstNames, srgField);
fillMappings(dstNames, newNsField);
}
if (srcDesc != null) {
flatOutput.visitField(srgClass.getSrcName(), srgField.getSrcName(), srcDesc, dstNames);
if (comment != null) flatOutput.visitFieldComment(srgClass.getSrcName(), srgField.getSrcName(), srcDesc, comment);
flatOutput.visitField(newNsClass.getSrcName(), newNsField.getSrcName(), srcDesc, dstNames);
if (comment != null) flatOutput.visitFieldComment(newNsClass.getSrcName(), newNsField.getSrcName(), srcDesc, comment);
} else if (!lenient) {
throw new MappingException("Could not find descriptor for field " + srgClass.getDstName(0) + '.' + srgField.getDstName(0));
throw new MappingException("Could not find descriptor for field " + newNsClass.getDstName(0) + '.' + newNsField.getDstName(0));
}
}
private void mergeMethod(MappingTree.ClassMapping srgClass, MappingTree.MethodMapping srgMethod, @Nullable MappingTree.ClassMapping tinyClass) throws IOException {
String[] dstNames = createDstNameArray(srgMethod);
private void mergeMethod(MappingTree.ClassMapping newNsClass, MappingTree.MethodMapping newNsMethod, @Nullable MappingTree.ClassMapping tinyClass) throws IOException {
String[] dstNames = createDstNameArray(newNsMethod);
MappingTree.MethodMapping tinyMethod = null;
String intermediaryName, namedName;
String comment = null;
if (tinyClass != null) {
tinyMethod = tinyClass.getMethod(srgMethod.getSrcName(), srgMethod.getSrcDesc());
tinyMethod = tinyClass.getMethod(newNsMethod.getSrcName(), newNsMethod.getSrcDesc());
} else if (!lenient) {
throw new MappingException("Could not find method " + srgClass.getDstName(0) + '.' + srgMethod.getDstName(0) + ' ' + srgMethod.getDstDesc(0));
throw new MappingException("Could not find method " + newNsClass.getDstName(0) + '.' + newNsMethod.getDstName(0) + ' ' + newNsMethod.getDstDesc(0));
}
if (tinyMethod != null) {
@@ -224,7 +229,7 @@ public final class SrgMerger {
namedName = tinyMethod.getName("named");
comment = tinyMethod.getComment();
} else {
if (srgMethod.getSrcName().equals(srgMethod.getDstName(0))) {
if (newNsMethod.getSrcName().equals(newNsMethod.getDstName(0))) {
// These are only methods like <init> or toString which have the same name in every NS.
// We can safely ignore those.
return;
@@ -233,7 +238,7 @@ public final class SrgMerger {
@Nullable MappingTree.MethodMapping fillMethod = null;
if (extra != null) {
MappingTree.MethodMapping extraMethod = extra.getMethod(srgClass.getSrcName(), srgMethod.getSrcName(), srgMethod.getSrcDesc());
MappingTree.MethodMapping extraMethod = extra.getMethod(newNsClass.getSrcName(), newNsMethod.getSrcName(), newNsMethod.getSrcDesc());
if (extraMethod != null && extraMethod.getSrcName().equals(extraMethod.getDstName(0))) {
fillMethod = extraMethod;
@@ -246,33 +251,33 @@ public final class SrgMerger {
} else {
// Do not allow missing methods as these are typically subclass methods and cause issues where
// class B extends A, and overrides a method from the superclass. Then the subclass method
// DOES get a srg name but not a yarn/intermediary name.
// DOES get a new namespace name but not a yarn/intermediary name.
return;
}
}
if (!srgMethod.getSrcName().equals(dstNames[0])) { // ignore <init> and the likes
methodsBySrgName.put(
new SrgMethodKey(dstNames[0], srgMethod.getSrcDesc()),
new MethodData(srgClass.getSrcName(), srgMethod.getSrcName(), srgMethod.getSrcDesc(), tinyMethod != null, intermediaryName, namedName)
if (!newNsMethod.getSrcName().equals(dstNames[0])) { // ignore <init> and the likes
methodsByNewNs.put(
new MethodKey(dstNames[0], newNsMethod.getSrcDesc()),
new MethodData(newNsClass.getSrcName(), newNsMethod.getSrcName(), newNsMethod.getSrcDesc(), tinyMethod != null, intermediaryName, namedName)
);
}
flatOutput.visitMethod(srgClass.getSrcName(), srgMethod.getSrcName(), srgMethod.getSrcDesc(), dstNames);
if (comment != null) flatOutput.visitMethodComment(srgClass.getSrcName(), srgMethod.getSrcName(), srgMethod.getSrcDesc(), comment);
flatOutput.visitMethod(newNsClass.getSrcName(), newNsMethod.getSrcName(), newNsMethod.getSrcDesc(), dstNames);
if (comment != null) flatOutput.visitMethodComment(newNsClass.getSrcName(), newNsMethod.getSrcName(), newNsMethod.getSrcDesc(), comment);
if (tinyMethod != null) {
for (MappingTree.MethodArgMapping arg : tinyMethod.getArgs()) {
String[] argDstNames = new String[output.getDstNamespaces().size()];
copyDstNames(argDstNames, arg);
flatOutput.visitMethodArg(
srgClass.getSrcName(), srgMethod.getSrcName(), srgMethod.getSrcDesc(),
newNsClass.getSrcName(), newNsMethod.getSrcName(), newNsMethod.getSrcDesc(),
arg.getArgPosition(), arg.getLvIndex(), arg.getSrcName(), argDstNames
);
if (arg.getComment() != null) {
flatOutput.visitMethodArgComment(
srgClass.getSrcName(), srgMethod.getSrcName(), srgMethod.getSrcDesc(),
newNsClass.getSrcName(), newNsMethod.getSrcName(), newNsMethod.getSrcDesc(),
arg.getArgPosition(), arg.getLvIndex(), arg.getSrcName(),
arg.getComment()
);
@@ -282,14 +287,14 @@ public final class SrgMerger {
}
/**
* Resolves conflicts where multiple methods map to an SRG method.
* Resolves conflicts where multiple methods map to a method in the new namespace.
* We will prefer the ones with the Tiny mappings.
*/
private void resolveConflicts() {
List<String> conflicts = new ArrayList<>();
for (SrgMethodKey srg : methodsBySrgName.keySet()) {
List<MethodData> methods = methodsBySrgName.get(srg);
for (MethodKey methodKey : methodsByNewNs.keySet()) {
List<MethodData> methods = methodsByNewNs.get(methodKey);
if (methods.size() == 1) continue;
// Determine whether the names conflict
@@ -326,8 +331,8 @@ public final class SrgMerger {
List<MethodData> hasTiny = CollectionUtil.filter(methods, MethodData::hasTiny);
// Record conflicts if needed
if (hasTiny.size() > 1) { // Multiple methods map to this SRG name
// Sometimes unrelated methods share an SRG name. Probably overrides from a past version?
if (hasTiny.size() > 1) { // Multiple methods map to this new name
// Sometimes unrelated methods share a SRG name. Probably overrides from a past version?
Set<String> intermediaryNames = new HashSet<>();
for (MethodData method : methods) {
@@ -337,7 +342,7 @@ public final class SrgMerger {
// Only record a conflict if we map one intermediary name with multiple named names
if (intermediaryNames.size() == 1) {
StringBuilder message = new StringBuilder();
message.append("- multiple preferred methods for ").append(srg).append(':');
message.append("- multiple preferred methods for ").append(newNs).append(':');
for (MethodData preferred : hasTiny) {
message.append("\n\t> ").append(preferred);
@@ -347,50 +352,14 @@ public final class SrgMerger {
}
return null;
} else if (hasTiny.isEmpty()) { // No methods map to this SRG name
conflictReporter.accept("- no preferred methods found for " + srg + ", available: " + methods);
} else if (hasTiny.isEmpty()) { // No methods map to this new name
conflictReporter.accept("- no preferred methods found for " + newNs + ", available: " + methods);
return null;
}
return hasTiny.get(0);
}
/**
* Merges SRG mappings with a tiny mappings tree through the obf names.
* This overload writes the mappings into a file.
*
* <p>The namespaces in the tiny file should be {@code official, intermediary, named}.
* The SRG names will add a new namespace called {@code srg} so that the final namespaces
* are {@code official, srg, intermediary, named}.
*
* <p>This method does not include local variables in the output.
* It can, however, be used for remapping the game jar since it has all
* classes, methods, fields, parameters and javadoc comments.
*
* <p>If {@code lenient} is true, the merger will not error when encountering names not present
* in the tiny mappings. Instead, the names will be filled from the {@code official} namespace.
*
* @param srg the SRG file in .tsrg format
* @param tiny the tiny file
* @param out the output file, will be in tiny v2
* @param extraMappings an extra mappings file that will be used to determine
* whether an unobfuscated name is needed in the result file
* @param lenient whether lenient mode is enabled
* @throws IOException if an IO error occurs while reading or writing the mappings
* @throws MappingException if the input tiny mappings' namespaces are incorrect
* or if an element mentioned in the SRG file does not have tiny mappings in non-lenient mode
*/
public static void mergeSrg(Path srg, Path tiny, Path out, @Nullable ExtraMappings extraMappings, boolean lenient)
throws IOException, MappingException {
MemoryMappingTree tree = mergeSrg(srg, tiny, extraMappings, lenient);
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out), false)) {
tree.accept(writer);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Merges SRG mappings with a tiny mappings tree through the obf names.
* This overload returns a {@link MemoryMappingTree} of the merged mappings.
@@ -418,20 +387,29 @@ public final class SrgMerger {
*/
public static MemoryMappingTree mergeSrg(Path srg, Path tiny, @Nullable ExtraMappings extraMappings, boolean lenient)
throws IOException, MappingException {
return new SrgMerger(srg, tiny, extraMappings, lenient).merge();
return new ForgeMappingsMerger(readSrg(srg), readInput(tiny), extraMappings, lenient).merge();
}
private MemoryMappingTree readSrg(Path srg) throws IOException {
public static MemoryMappingTree mergeMojang(MappingContext context, Path tiny, @Nullable ExtraMappings extraMappings, boolean lenient)
throws IOException, MappingException {
MemoryMappingTree mojang = new MemoryMappingTree();
SrgProvider.visitMojangMappings(new MappingNsRenamer(mojang, Map.of(MappingsNamespace.NAMED.toString(), MappingsNamespace.MOJANG.toString())), context);
return new ForgeMappingsMerger(mojang, readInput(tiny), extraMappings, lenient).merge();
}
private static MemoryMappingTree readSrg(Path srg) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(srg)) {
MemoryMappingTree tsrg = new MemoryMappingTree();
MemoryMappingTree temp = new MemoryMappingTree();
TsrgReader.read(reader, temp);
Map<String, String> namespaces = Map.of(
temp.getSrcNamespace(), MappingsNamespace.OFFICIAL.toString(),
temp.getDstNamespaces().get(0), MappingsNamespace.SRG.toString()
);
temp.accept(new MappingNsRenamer(tsrg, namespaces));
return tsrg;
MemoryMappingTree output = new MemoryMappingTree();
TsrgReader.read(reader, new ForwardingMappingVisitor(output) {
// Override the namespaces to be official -> srg
@Override
public void visitNamespaces(String srcNamespace, List<String> dstNamespaces) throws IOException {
List<String> newDstNamespaces = new ArrayList<>(dstNamespaces);
newDstNamespaces.set(0, MappingsNamespace.SRG.toString());
super.visitNamespaces(MappingsNamespace.OFFICIAL.toString(), newDstNamespaces);
}
});
return output;
}
}
@@ -445,7 +423,7 @@ public final class SrgMerger {
}
}
private record SrgMethodKey(String name, String desc) {
private record MethodKey(String name, String desc) {
@Override
public String toString() {
return name + desc;