aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/net/fabricmc/loom/configuration
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/net/fabricmc/loom/configuration')
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java63
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java2
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java2
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java13
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/MavenPublication.java23
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java18
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java9
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java64
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java12
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java78
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java85
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java4
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java63
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java29
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java281
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java87
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java61
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java105
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java72
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java694
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java90
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java78
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilder.java16
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java241
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java116
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java35
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java5
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java2
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java7
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java41
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java295
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java125
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/MercuryUtils.java42
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java82
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java237
35 files changed, 2995 insertions, 182 deletions
diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
index 549447a7..e6589da5 100644
--- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
@@ -40,7 +40,15 @@ import net.fabricmc.loom.build.mixin.ScalaApInvoker;
import net.fabricmc.loom.configuration.ide.SetupIntelijRunConfigs;
import net.fabricmc.loom.configuration.providers.LaunchProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
+import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProvider;
+import net.fabricmc.loom.configuration.providers.forge.ForgeProvider;
+import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider;
+import net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider;
+import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider;
+import net.fabricmc.loom.configuration.providers.forge.PatchProvider;
+import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
+import net.fabricmc.loom.task.GenVsCodeProjectTask;
import net.fabricmc.loom.util.Constants;
public final class CompileConfiguration {
@@ -49,16 +57,48 @@ public final class CompileConfiguration {
public static void setupConfigurations(Project project) {
final ConfigurationContainer configurations = project.getConfigurations();
-
LoomGradleExtension extension = LoomGradleExtension.get(project);
+ project.afterEvaluate(project1 -> {
+ if (extension.shouldGenerateSrgTiny()) {
+ extension.createLazyConfiguration(Constants.Configurations.SRG).configure(configuration -> configuration.setTransitive(false));
+ }
+
+ if (extension.isDataGenEnabled()) {
+ project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main").resources(files -> {
+ files.srcDir(project.file("src/generated/resources"));
+ });
+ }
+ });
+
extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH).configure(configuration -> configuration.setTransitive(true));
extension.createLazyConfiguration(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED).configure(configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_NAMED).configure(configuration -> configuration.setTransitive(false)); // The launchers do not recurse dependencies
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT_DEPENDENCIES).configure(configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.LOADER_DEPENDENCIES).configure(configuration -> configuration.setTransitive(false));
extension.createLazyConfiguration(Constants.Configurations.MINECRAFT).configure(configuration -> configuration.setTransitive(false));
- extension.createLazyConfiguration(Constants.Configurations.INCLUDE).configure(configuration -> configuration.setTransitive(false)); // Dont get transitive deps
+
+ if (extension.isForge()) {
+ extension.createLazyConfiguration(Constants.Configurations.FORGE).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.FORGE_USERDEV).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.FORGE_INSTALLER).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.FORGE_UNIVERSAL).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.FORGE_DEPENDENCIES).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.FORGE_NAMED).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.MCP_CONFIG).configure(configuration -> configuration.setTransitive(false));
+
+ extendsFrom(Constants.Configurations.MINECRAFT_DEPENDENCIES, Constants.Configurations.FORGE_DEPENDENCIES, project);
+
+ extendsFrom(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_NAMED, project);
+ extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_NAMED, project);
+ extendsFrom(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_NAMED, project);
+ extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_NAMED, project);
+ }
+
+ if (extension.supportsInclude()) {
+ extension.createLazyConfiguration(Constants.Configurations.INCLUDE).configure(configuration -> configuration.setTransitive(false)); // Dont get transitive deps
+ }
+
extension.createLazyConfiguration(Constants.Configurations.MAPPING_CONSTANTS);
extendsFrom(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, Constants.Configurations.MAPPING_CONSTANTS, project);
@@ -113,7 +153,23 @@ public final class CompileConfiguration {
extension.setDependencyManager(dependencyManager);
dependencyManager.addProvider(new MinecraftProviderImpl(project));
- dependencyManager.addProvider(new MappingsProviderImpl(project));
+
+ if (extension.isForge()) {
+ dependencyManager.addProvider(new ForgeProvider(project));
+ dependencyManager.addProvider(new ForgeUserdevProvider(project));
+ }
+
+ if (extension.shouldGenerateSrgTiny()) {
+ dependencyManager.addProvider(new SrgProvider(project));
+ }
+
+ if (extension.isForge()) {
+ dependencyManager.addProvider(new McpConfigProvider(project));
+ dependencyManager.addProvider(new PatchProvider(project));
+ dependencyManager.addProvider(new ForgeUniversalProvider(project));
+ }
+
+ dependencyManager.addProvider(extension.isForge() ? new FieldMigratedMappingsProvider(project) : new MappingsProviderImpl(project));
dependencyManager.addProvider(new LaunchProvider(project));
dependencyManager.handleDependencies(project);
@@ -123,6 +179,7 @@ public final class CompileConfiguration {
project.getTasks().getByName("cleanEclipse").finalizedBy(project.getTasks().getByName("cleanEclipseRuns"));
SetupIntelijRunConfigs.setup(project);
+ GenVsCodeProjectTask.generate(project);
extension.getRemapArchives().finalizeValue();
// Enables the default mod remapper
diff --git a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
index 9a123adc..5017d54a 100644
--- a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
@@ -195,7 +195,7 @@ public abstract class DependencyProvider {
this.resolvedFiles = files;
switch (files.size()) {
case 0 -> //Don't think Gradle would ever let you do this
- throw new IllegalStateException("Empty dependency?");
+ throw new IllegalStateException("Empty dependency for " + configuration.getName());
case 1 -> //Single file dependency
classifierToFile.put("", Iterables.getOnlyElement(files));
default -> { //File collection, try work out the classifiers
diff --git a/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java
index 5048a0f9..408081d7 100644
--- a/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java
@@ -28,13 +28,13 @@ import java.util.Optional;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
+import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
import org.gradle.util.GradleVersion;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.util.Constants;
-import net.fabricmc.tinyremapper.TinyRemapper;
public final record JarManifestConfiguration(Project project) {
public void configure(Manifest manifest) {
diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
index e3a4c565..b14d2678 100644
--- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
+++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
@@ -74,7 +74,7 @@ public class LoomDependencyManager {
return provider;
}
- public <T> T getProvider(Class<T> clazz) {
+ public <T> T getProvider(Class<? extends T> clazz) {
for (DependencyProvider provider : dependencyProviderList) {
if (provider.getClass() == clazz) {
return (T) provider;
@@ -132,16 +132,17 @@ public class LoomDependencyManager {
try {
provider.provide(info, afterTasks::add);
} catch (Exception e) {
- throw new RuntimeException("Failed to provide " + dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion() + " : " + e.toString(), e);
+ throw new RuntimeException("Failed to provide " + dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion() + " : " + e.toString() + "\n\tEnsure minecraft is not open and try running with --refresh-dependencies. Use --stacktrace to see the full stacktrace.", e);
}
}
}
}
SourceRemapper sourceRemapper = new SourceRemapper(project, true);
- String mappingsKey = mappingsProvider.getMappingsKey();
+ String platformSuffix = extension.isForge() ? "_forge" : "";
+ String mappingsKey = mappingsProvider.getMappingsKey() + platformSuffix;
- if (extension.getInstallerData() == null) {
+ if (extension.getInstallerData() == null && !extension.isForge()) {
//If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking
project.getLogger().info("Searching through modCompileClasspath for installer JSON");
final Configuration configuration = project.getConfigurations().getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH);
@@ -162,10 +163,10 @@ public class LoomDependencyManager {
}
}
}
- }
if (extension.getInstallerData() == null) {
- project.getLogger().warn("fabric-installer.json not found in classpath!");
+ project.getLogger().warn("fabric-installer.json not found in classpath!");
+ }
}
ModCompileRemapper.remapDependencies(project, mappingsKey, extension, sourceRemapper);
diff --git a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java
index 35dd1d83..178f9aa7 100644
--- a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java
+++ b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java
@@ -34,6 +34,7 @@ import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ExcludeRule;
import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.logging.Logger;
import org.gradle.api.publish.Publication;
import org.gradle.api.publish.PublishingExtension;
@@ -46,6 +47,14 @@ public final class MavenPublication {
public static void configure(Project project) {
project.afterEvaluate((p) -> {
+ // add modsCompile to maven-publish
+ PublishingExtension mavenPublish = p.getExtensions().findByType(PublishingExtension.class);
+
+ if (mavenPublish == null) {
+ p.getLogger().info("No maven publications for project [" + p.getName() + "], skipping configuration.");
+ return;
+ }
+
for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) {
if (!entry.hasMavenScope()) {
continue;
@@ -53,23 +62,20 @@ public final class MavenPublication {
Configuration compileModsConfig = p.getConfigurations().getByName(entry.sourceConfiguration());
- // add modsCompile to maven-publish
- PublishingExtension mavenPublish = p.getExtensions().findByType(PublishingExtension.class);
-
- if (mavenPublish != null) {
- processEntry(entry, compileModsConfig, mavenPublish);
- }
+ p.getLogger().info("Processing maven publication for project [" + p.getName() + "] of " + entry.sourceConfiguration());
+ processEntry(p.getLogger(), entry, compileModsConfig, mavenPublish);
}
});
}
- private static void processEntry(RemappedConfigurationEntry entry, Configuration compileModsConfig, PublishingExtension mavenPublish) {
+ private static void processEntry(Logger logger, RemappedConfigurationEntry entry, Configuration compileModsConfig, PublishingExtension mavenPublish) {
mavenPublish.publications((publications) -> {
for (Publication publication : publications) {
if (!(publication instanceof org.gradle.api.publish.maven.MavenPublication)) {
continue;
}
+ logger.info("Processing maven publication [" + publication.getName() + "]");
((org.gradle.api.publish.maven.MavenPublication) publication).pom((pom) -> pom.withXml((xml) -> {
Node dependencies = GroovyXmlUtil.getOrCreateNode(xml.asNode(), "dependencies");
Set<String> foundArtifacts = new HashSet<>();
@@ -85,9 +91,12 @@ public final class MavenPublication {
for (Dependency dependency : compileModsConfig.getAllDependencies()) {
if (foundArtifacts.contains(dependency.getGroup() + ":" + dependency.getName())) {
+ logger.info("Found inserted artifact " + dependency.getGroup() + ":" + dependency.getName());
continue;
}
+ logger.info("Inserting artifact " + dependency.getGroup() + ":" + dependency.getName());
+
Node depNode = dependencies.appendNode("dependency");
depNode.appendNode("groupId", dependency.getGroup());
depNode.appendNode("artifactId", dependency.getName());
diff --git a/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java
index f3c16aef..6afcf2c1 100644
--- a/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java
@@ -27,12 +27,14 @@ package net.fabricmc.loom.configuration;
import java.io.IOException;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.UnknownTaskException;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.api.tasks.bundling.Jar;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.LoomGradleExtension;
@@ -75,6 +77,12 @@ public class RemapConfiguration {
remapJarTask.getInput().set(jarTask.getArchivePath());
}
+ if (extension.isForge()) {
+ ((Jar) jarTask).manifest(manifest -> {
+ manifest.attributes(ImmutableMap.of("MixinConfigs", String.join(",", extension.getMixinConfigs())));
+ });
+ }
+
if (isDefaultRemap) {
extension.getUnmappedModCollection().from(jarTask);
remapJarTask.getAddNestedDependencies().set(true);
@@ -88,7 +96,7 @@ public class RemapConfiguration {
// TODO this might be wrong?
project.getTasks().withType(RemapJarTask.class).forEach(task -> {
- if (task.getAddNestedDependencies().getOrElse(false)) {
+ if (extension.supportsInclude() && task.getAddNestedDependencies().getOrElse(false)) {
NestedDependencyProvider.getRequiredTasks(project).forEach(task::dependsOn);
}
});
@@ -115,7 +123,7 @@ public class RemapConfiguration {
rootProject.getTasks().register(remapAllJarsTaskName, AbstractLoomTask.class, task -> {
task.doLast(t -> {
try {
- jarRemapper.remap();
+ jarRemapper.remap(project);
} catch (IOException e) {
throw new RuntimeException("Failed to remap jars", e);
}
@@ -139,9 +147,11 @@ public class RemapConfiguration {
RemapSourcesJarTask remapSourcesJarTask = (RemapSourcesJarTask) project.getTasks().findByName(remapSourcesJarTaskName);
Preconditions.checkNotNull(remapSourcesJarTask, "Could not find " + remapSourcesJarTaskName + " in " + project.getName());
- remapSourcesJarTask.setInput(sourcesTask.getArchivePath());
remapSourcesJarTask.setOutput(sourcesTask.getArchivePath());
- remapSourcesJarTask.dependsOn(project.getTasks().getByName(sourcesJarTaskName));
+ String sourcesTaskClassifer = sourcesTask.getArchiveClassifier().get();
+ sourcesTask.getArchiveClassifier().set(sourcesTaskClassifer == null ? "dev" : sourcesTaskClassifer + "-dev");
+ remapSourcesJarTask.setInput(sourcesTask.getArchivePath());
+ remapSourcesJarTask.dependsOn(sourcesTask);
if (isDefaultRemap) {
// Do not use lambda here, see: https://github.com/gradle/gradle/pull/17087
diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java
index bca3f352..cecf4d34 100644
--- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java
+++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java
@@ -38,6 +38,7 @@ import java.util.zip.ZipEntry;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
+import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@@ -55,9 +56,9 @@ import net.fabricmc.accesswidener.AccessWidenerVisitor;
import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.processors.JarProcessor;
+import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.util.Constants;
-import net.fabricmc.tinyremapper.TinyRemapper;
public class AccessWidenerJarProcessor implements JarProcessor {
private AccessWidener accessWidener = new AccessWidener();
@@ -100,8 +101,10 @@ public class AccessWidenerJarProcessor implements JarProcessor {
throw new UnsupportedOperationException(String.format("Access Widener namespace '%s' is not a valid namespace, it must be one of: '%s'", accessWidener.getNamespace(), String.join(", ", validNamespaces)));
}
- TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper("official", "named");
- tinyRemapper.readClassPath(loomGradleExtension.getMinecraftMappedProvider().getRemapClasspath());
+ TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper();
+ tinyRemapper.replaceMappings(loomGradleExtension.getMinecraftMappedProvider().getMappings(null, "official", "named"));
+ loomGradleExtension.getMinecraftMappedProvider();
+ tinyRemapper.readClassPath(MinecraftMappedProvider.getRemapClasspath(project));
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, tinyRemapper.getRemapper(), "named");
accessWidener = remapper.remap();
diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
index cf78e568..8c60619f 100644
--- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
+++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
@@ -24,11 +24,21 @@
package net.fabricmc.loom.configuration.ide;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+import java.util.HashMap;
import java.util.Objects;
+import java.util.UUID;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
@@ -49,11 +59,14 @@ public class RunConfig {
public String configName;
public String eclipseProjectName;
public String ideaModuleName;
+ public String vscodeProjectName;
public String mainClass;
public String runDirIdeaUrl;
public String runDir;
public String vmArgs;
public String programArgs;
+ public List<String> vscodeBeforeRun = new ArrayList<>();
+ public final Map<String, String> envVariables = new HashMap<>();
public SourceSet sourceSet;
public Element genRuns(Element doc) {
@@ -72,6 +85,14 @@ public class RunConfig {
this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", programArgs));
}
+ if (!envVariables.isEmpty()) {
+ Element envs = this.addXml(root, "envs", ImmutableMap.of());
+
+ for (Map.Entry<String, String> envEntry : envVariables.entrySet()) {
+ this.addXml(envs, "env", ImmutableMap.of("name", envEntry.getKey(), "value", envEntry.getValue()));
+ }
+ }
+
return root;
}
@@ -105,11 +126,29 @@ public class RunConfig {
private static void populate(Project project, LoomGradleExtension extension, RunConfig runConfig, String environment) {
runConfig.configName += extension.isRootProject() ? "" : " (" + project.getPath() + ")";
runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName();
+ runConfig.vscodeProjectName = extension.isRootProject() ? "" : project.getPath();
runConfig.vmArgs = "";
runConfig.programArgs = "";
runConfig.mainClass = "net.fabricmc.devlaunchinjector.Main";
runConfig.vmArgs = "-XX:+ShowCodeDetailsInExceptionMessages -Dfabric.dli.config=" + encodeEscaped(extension.getFiles().getDevLauncherConfig().getAbsolutePath()) + " -Dfabric.dli.env=" + environment.toLowerCase();
+
+ if (extension.isForge()) {
+ List<String> modClasses = new ArrayList<>();
+
+ for (Supplier<SourceSet> sourceSetSupplier : extension.getForgeLocalMods()) {
+ SourceSet sourceSet = sourceSetSupplier.get();
+ String sourceSetName = sourceSet.getName() + "_" + UUID.randomUUID().toString().replace("-", "").substring(0, 7);
+
+ Stream.concat(
+ Stream.of(sourceSet.getOutput().getResourcesDir().getAbsolutePath()),
+ StreamSupport.stream(sourceSet.getOutput().getClassesDirs().spliterator(), false)
+ .map(File::getAbsolutePath)
+ ).map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses));
+ }
+
+ runConfig.envVariables.put("MOD_CLASSES", String.join(File.pathSeparator, modClasses));
+ }
}
// Turns camelCase/PascalCase into Capital Case
@@ -178,6 +217,10 @@ public class RunConfig {
runConfig.programArgs = runConfig.programArgs.trim();
runConfig.vmArgs = runConfig.vmArgs.trim();
+ for (Consumer<RunConfig> consumer : extension.getSettingsPostEdit()) {
+ consumer.accept(runConfig);
+ }
+
return runConfig;
}
@@ -196,6 +239,25 @@ public class RunConfig {
dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", programArgs.replaceAll("\"", "&quot;"));
dummyConfig = dummyConfig.replace("%VM_ARGS%", vmArgs.replaceAll("\"", "&quot;"));
+ String envs = "";
+
+ if (!envVariables.isEmpty()) {
+ StringBuilder builder = new StringBuilder("<envs>");
+
+ for (Map.Entry<String, String> env : envVariables.entrySet()) {
+ builder.append("<env name=\"");
+ builder.append(env.getKey().replaceAll("\"", "&quot;"));
+ builder.append("\" value=\"");
+ builder.append(env.getValue().replaceAll("\"", "&quot;"));
+ builder.append("\"/>");
+ }
+
+ builder.append("</envs>");
+ envs = builder.toString();
+ }
+
+ dummyConfig = dummyConfig.replace("%ENVS%", envs);
+
return dummyConfig;
}
@@ -208,7 +270,7 @@ public class RunConfig {
}
private static String getMainClass(String side, LoomGradleExtension extension, String defaultMainClass) {
- InstallerData installerData = extension.getInstallerData();
+ InstallerData installerData = extension.getInstallerData() == null ? null : extension.getInstallerData();
if (installerData == null) {
return defaultMainClass;
diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java
index 60548c2b..28f2f2f6 100644
--- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java
+++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java
@@ -262,7 +262,7 @@ public final class RunConfigSettings implements Named {
public void client() {
startFirstThread();
environment("client");
- defaultMainClass(Constants.Knot.KNOT_CLIENT);
+ defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_CLIENT);
}
/**
@@ -271,7 +271,15 @@ public final class RunConfigSettings implements Named {
public void server() {
programArg("nogui");
environment("server");
- defaultMainClass(Constants.Knot.KNOT_SERVER);
+ defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER);
+ }
+
+ /**
+ * Configure run config with the default server options.
+ */
+ public void data() {
+ environment("data");
+ defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER);
}
/**
diff --git a/src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java b/src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java
new file mode 100644
index 00000000..3a0c8eca
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2021 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.launch;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.gradle.api.Named;
+import org.gradle.api.Project;
+
+public class LaunchProviderSettings implements Named {
+ private final String name;
+ private List<Map.Entry<String, String>> properties = new ArrayList<>();
+ private List<String> arguments = new ArrayList<>();
+
+ public LaunchProviderSettings(Project project, String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public void arg(String argument) {
+ this.arguments.add(argument);
+ }
+
+ public void arg(String... arguments) {
+ this.arguments.addAll(Arrays.asList(arguments));
+ }
+
+ public void arg(Collection<String> arguments) {
+ this.arguments.addAll(arguments);
+ }
+
+ public void property(String key, String value) {
+ this.properties.add(new AbstractMap.SimpleEntry<>(key, value));
+ }
+
+ public void properties(Map<String, String> arguments) {
+ this.properties.addAll(arguments.entrySet());
+ }
+
+ public List<Map.Entry<String, String>> getProperties() {
+ return properties;
+ }
+
+ public List<String> getArguments() {
+ return arguments;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
index 808c857c..b7b37506 100644
--- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
+++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
@@ -35,13 +35,21 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.jar.Attributes;
import java.util.jar.JarFile;
+import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
+import com.google.common.base.Stopwatch;
import com.google.gson.JsonObject;
+import dev.architectury.tinyremapper.InputTag;
+import dev.architectury.tinyremapper.OutputConsumerPath;
+import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
import org.objectweb.asm.commons.Remapper;
import org.zeroturnaround.zip.ZipUtil;
@@ -59,10 +67,11 @@ import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.LoggerFilter;
import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
-import net.fabricmc.tinyremapper.InputTag;
-import net.fabricmc.tinyremapper.OutputConsumerPath;
-import net.fabricmc.tinyremapper.TinyRemapper;
+import net.fabricmc.loom.util.srg.AtRemapper;
+import net.fabricmc.loom.util.srg.CoreModClassRemapper;
+import net.fabricmc.mapping.tree.TinyTree;
public class ModProcessor {
public static void processMods(Project project, List<ModDependencyInfo> processList) throws IOException {
@@ -96,6 +105,7 @@ public class ModProcessor {
}
private static void stripNestedJars(File file) {
+ if (!ZipUtil.containsEntry(file, "fabric.mod.json")) return;
// Strip out all contained jar info as we dont want loader to try and load the jars contained in dev.
ZipUtil.transformEntries(file, new ZipEntryTransformerEntry[] {(new ZipEntryTransformerEntry("fabric.mod.json", new StringZipEntryTransformer() {
@Override
@@ -128,26 +138,36 @@ public class ModProcessor {
private static void remapJars(Project project, List<ModDependencyInfo> processList) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
- String fromM = "intermediary";
+ String fromM = extension.isForge() ? "srg" : "intermediary";
String toM = "named";
MinecraftMappedProvider mappedProvider = extension.getMinecraftMappedProvider();
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
- Path mc = mappedProvider.getIntermediaryJar().toPath();
+ Path mc = extension.isForge() ? mappedProvider.getSrgJar().toPath() : mappedProvider.getIntermediaryJar().toPath();
Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles()
- .stream().map(File::toPath).toArray(Path[]::new);
+ .stream().map(File::toPath).toArray(Path[]::new);
List<ModDependencyInfo> remapList = processList.stream().filter(ModDependencyInfo::requiresRemapping).collect(Collectors.toList());
+ Stopwatch stopwatch = Stopwatch.createStarted();
project.getLogger().lifecycle(":remapping " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ")");
+ TinyTree mappings = extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings();
+ LoggerFilter.replaceSystemOut();
TinyRemapper remapper = TinyRemapper.newRemapper()
- .withMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false))
- .renameInvalidLocals(false)
- .build();
+ .logger(project.getLogger()::lifecycle)
+ .logUnknownInvokeDynamic(false)
+ .withMappings(TinyRemapperMappingsHelper.create(mappings, fromM, toM, false))
+ .renameInvalidLocals(false)
+ .build();
remapper.readClassPathAsync(mc);
+
+ if (extension.isForge()) {
+ remapper.readClassPathAsync(mappedProvider.getForgeSrgJar().toPath());
+ }
+
remapper.readClassPathAsync(mcDeps);
final Map<ModDependencyInfo, InputTag> tagMap = new HashMap<>();
@@ -195,6 +215,7 @@ public class ModProcessor {
}
remapper.finish();
+ project.getLogger().lifecycle(":remapped " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch.stop());
for (ModDependencyInfo info : remapList) {
outputConsumerMap.get(info).close();
@@ -204,10 +225,56 @@ public class ModProcessor {
ZipUtil.replaceEntry(info.getRemappedOutput(), info.getAccessWidener(), accessWidener);
}
+ if (extension.isForge()) {
+ AtRemapper.remap(project.getLogger(), info.getRemappedOutput().toPath(), mappings);
+ CoreModClassRemapper.remapJar(info.getRemappedOutput().toPath(), mappings, project.getLogger());
+
+ if (ZipUtil.containsEntry(info.getRemappedOutput(), "META-INF/MANIFEST.MF")) {
+ ZipUtil.transformEntry(info.getRemappedOutput(), "META-INF/MANIFEST.MF", (in, zipEntry, out) -> {
+ Manifest manifest = new Manifest(in);
+ fixManifest(manifest);
+ out.putNextEntry(new ZipEntry(zipEntry.getName()));
+ manifest.write(out);
+ out.closeEntry();
+ });
+ }
+
+ List<String> filesToRemove = new ArrayList<>();
+ ZipUtil.iterate(info.getRemappedOutput(), (in, zipEntry) -> {
+ if (zipEntry.getName().toLowerCase(Locale.ROOT).endsWith(".rsa") || zipEntry.getName().toLowerCase(Locale.ROOT).endsWith(".sf")) {
+ if (zipEntry.getName().startsWith("META-INF")) {
+ filesToRemove.add(zipEntry.getName());
+ }
+ }
+ });
+ ZipUtil.removeEntries(info.getRemappedOutput(), filesToRemove.toArray(new String[0]));
+ }
+
info.finaliseRemapping();
}
}
+ private static void fixManifest(Manifest manifest) {
+ Attributes mainAttrs = manifest.getMainAttributes();
+
+ mainAttrs.remove(Attributes.Name.SIGNATURE_VERSION);
+
+ for (Iterator<Attributes> it = manifest.getEntries().values().iterator(); it.hasNext(); ) {
+ Attributes attrs = it.next();
+
+ for (Iterator<Object> it2 = attrs.keySet().iterator(); it2.hasNext(); ) {
+ Attributes.Name attrName = (Attributes.Name) it2.next();
+ String name = attrName.toString();
+
+ if (name.endsWith("-Digest") || name.contains("-Digest-") || name.equals("Magic")) {
+ it2.remove();
+ }
+ }
+
+ if (attrs.isEmpty()) it.remove();
+ }
+ }
+
public static JsonObject readInstallerJson(File file, Project project) {
try {
LoomGradleExtension extension = LoomGradleExtension.get(project);
diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java
index de10ba53..4e28f52a 100644
--- a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java
@@ -52,7 +52,9 @@ public class MinecraftProcessedProvider extends MinecraftMappedProvider {
@Override
protected void addDependencies(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) {
- if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps()) {
+ boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty();
+
+ if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps() || isForgeAtDirty) {
getProject().getLogger().info(":processing mapped jar");
invalidateJars();
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java
index b11e0116..f0d2b3a1 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java
@@ -44,6 +44,7 @@ import org.gradle.api.plugins.JavaPlugin;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
+import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.util.Constants;
public class LaunchProvider extends DependencyProvider {
@@ -66,6 +67,58 @@ public class LaunchProvider extends DependencyProvider {
.argument("client", "--assetsDir")
.argument("client", new File(getDirectories().getUserCache(), "assets").getAbsolutePath());
+ if (getExtension().isForge()) {
+ launchConfig
+ // Should match YarnNamingService.PATH_TO_MAPPINGS in forge-runtime
+ .property("fabric.yarnWithSrg.path", getExtension().getMappingsProvider().tinyMappingsWithSrg.toAbsolutePath().toString())
+
+ .argument("--fml.mcVersion")
+ .argument(getExtension().getMinecraftProvider().minecraftVersion())
+ .argument("--fml.forgeVersion")
+ .argument(getExtension().getForgeProvider().getVersion().getForgeVersion())
+
+ .argument("client", "--launchTarget")
+ .argument("client", "fmluserdevclient")
+
+ .argument("server", "--launchTarget")
+ .argument("server", "fmluserdevserver")
+
+ .argument("data", "--launchTarget")
+ .argument("data", "fmluserdevdata")
+ .argument("data", "--all")
+ .argument("data", "--mod")
+ .argument("data", String.join(",", getExtension().getDataGenMods()))
+ .argument("data", "--output")
+ .argument("data", getProject().file("src/generated/resources").getAbsolutePath())
+
+ .property("mixin.env.remapRefMap", "true");
+
+ if (getExtension().isUseFabricMixin()) {
+ launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingsProvider().mixinTinyMappingsWithSrg.getAbsolutePath());
+ } else {
+ launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.getAbsolutePath());
+ }
+
+ List<String> mixinConfigs = getExtension().getMixinConfigs();
+
+ if (mixinConfigs != null) {
+ for (String config : mixinConfigs) {
+ launchConfig.argument("-mixin.config");
+ launchConfig.argument(config);
+ }
+ }
+ }
+
+ for (LaunchProviderSettings settings : getExtension().getLaunchConfigs()) {
+ for (String argument : settings.getArguments()) {
+ launchConfig.argument(settings.getName(), argument);
+ }
+
+ for (Map.Entry<String, String> property : settings.getProperties()) {
+ launchConfig.property(settings.getName(), property.getKey(), property.getValue());
+ }
+ }
+
//Enable ansi by default for idea and vscode
if (new File(getProject().getRootDir(), ".vscode").exists()
|| new File(getProject().getRootDir(), ".idea").exists()
@@ -80,6 +133,12 @@ public class LaunchProvider extends DependencyProvider {
addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES);
addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME);
+ if (getExtension().isForge()) {
+ addDependency(Constants.Dependencies.FORGE_RUNTIME + Constants.Dependencies.Versions.FORGE_RUNTIME, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME);
+ addDependency(Constants.Dependencies.FORGE_RUNTIME + Constants.Dependencies.Versions.FORGE_RUNTIME, JavaPlugin.RUNTIME_ONLY_CONFIGURATION_NAME);
+ addDependency(Constants.Dependencies.JAVAX_ANNOTATIONS + Constants.Dependencies.Versions.JAVAX_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME);
+ }
+
postPopulationScheduler.accept(this::writeRemapClassPath);
}
@@ -119,6 +178,10 @@ public class LaunchProvider extends DependencyProvider {
remapClasspath.add(getExtension().getMinecraftMappedProvider().getIntermediaryJar());
+ if (getExtension().isForge()) {
+ remapClasspath.add(getExtension().getMinecraftMappedProvider().getForgeIntermediaryJar());
+ }
+
String str = remapClasspath.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator));
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java
index 97fa0013..2ac31e06 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java
@@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.function.Consumer;
+import com.google.common.base.Stopwatch;
import com.google.common.io.Files;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
@@ -54,11 +55,12 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
private MinecraftLibraryProvider libraryProvider;
private File minecraftJson;
- private File minecraftClientJar;
- private File minecraftServerJar;
+ public File minecraftClientJar;
+ public File minecraftServerJar;
private File minecraftMergedJar;
private File versionManifestJson;
private File experimentalVersionsJson;
+ private String jarSuffix = "";
public MinecraftProviderImpl(Project project) {
super(project);
@@ -68,6 +70,10 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
minecraftVersion = dependency.getDependency().getVersion();
+ if (getExtension().shouldGenerateSrgTiny() && !getExtension().isForge()) {
+ addDependency("de.oceanlabs.mcp:mcp_config:" + minecraftVersion, Constants.Configurations.SRG);
+ }
+
boolean offline = getProject().getGradle().getStartParameter().isOffline();
initFiles();
@@ -120,6 +126,14 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
experimentalVersionsJson = new File(getDirectories().getUserCache(), "experimental_version_manifest.json");
}
+ public void deleteFiles() {
+ DownloadUtil.delete(minecraftClientJar);
+ DownloadUtil.delete(minecraftServerJar);
+ DownloadUtil.delete(minecraftMergedJar);
+ DownloadUtil.delete(versionManifestJson);
+ DownloadUtil.delete(experimentalVersionsJson);
+ }
+
private void downloadMcJson(boolean offline) throws IOException {
if (getExtension().getShareRemapCaches().get() && !getExtension().isRootProject() && versionManifestJson.exists() && !isRefreshDeps()) {
return;
@@ -246,11 +260,14 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
private void mergeJars(Logger logger) throws IOException {
logger.info(":merging jars");
+ Stopwatch stopwatch = Stopwatch.createStarted();
try (JarMerger jarMerger = new JarMerger(minecraftClientJar, minecraftServerJar, minecraftMergedJar)) {
jarMerger.enableSyntheticParamsOffset();
jarMerger.merge();
}
+
+ logger.info(":merged jars in " + stopwatch);
}
public File getMergedJar() {
@@ -271,6 +288,14 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
return libraryProvider;
}
+ public String getJarSuffix() {
+ return jarSuffix;
+ }
+
+ public void setJarSuffix(String jarSuffix) {
+ this.jarSuffix = jarSuffix;
+ }
+
@Override
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT;
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java
new file mode 100644
index 00000000..24639702
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java
@@ -0,0 +1,281 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2021 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.forge;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import dev.architectury.mappingslayers.api.mutable.MappingsEntry;
+import dev.architectury.mappingslayers.api.mutable.MutableClassDef;
+import dev.architectury.mappingslayers.api.mutable.MutableFieldDef;
+import dev.architectury.mappingslayers.api.mutable.MutableTinyTree;
+import dev.architectury.mappingslayers.api.utils.MappingsUtils;
+import dev.architectury.refmapremapper.utils.DescriptorRemapper;
+import org.gradle.api.Project;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Opcodes;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.LoomGradlePlugin;
+import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.ThreadingUtils;
+import net.fabricmc.loom.util.srg.SrgMerger;
+import net.fabricmc.mapping.tree.ClassDef;
+import net.fabricmc.mapping.tree.FieldDef;
+import net.fabricmc.mapping.tree.TinyMappingFactory;
+import net.fabricmc.mapping.tree.TinyTree;
+
+public class FieldMigratedMappingsProvider extends MappingsProviderImpl {
+ private List<Map.Entry<FieldMember, String>> migratedFields = new ArrayList<>();
+ public Path migratedFieldsCache;
+ public Path rawTinyMappings;
+ public Path rawTinyMappingsWithSrg;
+
+ public FieldMigratedMappingsProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ LoomGradleExtension extension = getExtension();
+ PatchProvider patchProvider = getExtension().getPatchProvider();
+ migratedFieldsCache = patchProvider.getProjectCacheFolder().resolve("migrated-fields.json");
+ migratedFields.clear();
+
+ if (LoomGradlePlugin.refreshDeps) {
+ Files.deleteIfExists(migratedFieldsCache);
+ } else if (Files.exists(migratedFieldsCache)) {
+ try (BufferedReader reader = Files.newBufferedReader(migratedFieldsCache)) {
+ Map<String, String> map = new Gson().fromJson(reader, new TypeToken<Map<String, String>>() {
+ }.getType());
+ migratedFields = new ArrayList<>();
+ map.forEach((key, newDescriptor) -> {
+ String[] split = key.split("#");
+ migratedFields.add(new AbstractMap.SimpleEntry<>(new FieldMember(split[0], split[1]), newDescriptor));
+ });
+ }
+ }
+
+ super.provide(dependency, postPopulationScheduler);
+ }
+
+ @Override
+ public void manipulateMappings(Path mappingsJar) throws IOException {
+ LoomGradleExtension extension = getExtension();
+ Path mappingsFolder = getMappedVersionedDir(removeSuffix).resolve("forge/" + extension.getPatchProvider().forgeVersion);
+ this.rawTinyMappings = tinyMappings.toPath();
+ this.rawTinyMappingsWithSrg = tinyMappingsWithSrg;
+ String mappingsJarName = mappingsJar.getFileName().toString();
+
+ if (getExtension().shouldGenerateSrgTiny()) {
+ if (Files.notExists(rawTinyMappingsWithSrg) || isRefreshDeps()) {
+ // Merge tiny mappings with srg
+ SrgMerger.mergeSrg(getExtension().getSrgProvider().getSrg().toPath(), rawTinyMappings, rawTinyMappingsWithSrg, true);
+ }
+ }
+
+ try {
+ Files.createDirectories(mappingsFolder);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ tinyMappings = mappingsFolder.resolve("mappings.tiny").toFile();
+ tinyMappingsJar = mappingsFolder.resolve("mappings.jar").toFile();
+ tinyMappingsWithSrg = mappingsFolder.resolve("mappings-srg.tiny");
+ mixinTinyMappingsWithSrg = mappingsFolder.resolve("mixin-srg.tiny").toFile();
+ srgToNamedSrg = mappingsFolder.resolve("srg-to-named.srg").toFile();
+
+ try {
+ updateFieldMigration();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public void updateFieldMigration() throws IOException {
+ if (!Files.exists(migratedFieldsCache)) {
+ generateNewFieldMigration();
+ Map<String, String> map = new HashMap<>();
+ migratedFields.forEach(entry -> {
+ map.put(entry.getKey().owner + "#" + entry.getKey().field, entry.getValue());
+ });
+ Files.writeString(migratedFieldsCache, new Gson().toJson(map));
+ Files.deleteIfExists(tinyMappings.toPath());
+ }
+
+ if (!Files.exists(tinyMappings.toPath())) {
+ Table<String, String, String> fieldDescriptorMap = HashBasedTable.create();
+
+ for (Map.Entry<FieldMember, String> entry : migratedFields) {
+ fieldDescriptorMap.put(entry.getKey().owner, entry.getKey().field, entry.getValue());
+ }
+
+ MutableTinyTree mappings;
+
+ try (BufferedReader reader = Files.newBufferedReader(rawTinyMappings)) {
+ mappings = MappingsUtils.copyAsMutable(TinyMappingFactory.loadWithDetection(reader));
+
+ for (MutableClassDef classDef : mappings.getClassesMutable()) {
+ Map<String, String> row = fieldDescriptorMap.row(classDef.getIntermediary());
+
+ if (!row.isEmpty()) {
+ for (MutableFieldDef fieldDef : classDef.getFieldsMutable()) {
+ String newDescriptor = row.get(fieldDef.getIntermediary());
+
+ if (newDescriptor != null) {
+ fieldDef.setDescriptor(MappingsEntry.NS_INTERMEDIARY, newDescriptor);
+ }
+ }
+ }
+ }
+ }
+
+ Files.writeString(tinyMappings.toPath(), MappingsUtils.serializeToString(mappings), StandardOpenOption.CREATE);
+ }
+ }
+
+ private void generateNewFieldMigration() throws IOException {
+ Map<FieldMember, String> fieldDescriptorMap = new ConcurrentHashMap<>();
+ LoomGradleExtension extension = getExtension();
+ ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter();
+
+ class Visitor extends ClassVisitor {
+ private final ThreadLocal<String> lastClass = new ThreadLocal<>();
+
+ Visitor(int api) {
+ super(api);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ lastClass.set(name);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
+ fieldDescriptorMap.put(new FieldMember(lastClass.get(), name), descriptor);
+ return super.visitField(access, name, descriptor, signature, value);
+ }
+ }
+
+ Visitor visitor = new Visitor(Opcodes.ASM9);
+
+ for (MinecraftPatchedProvider.Environment environment : MinecraftPatchedProvider.Environment.values()) {
+ File patchedSrgJar = environment.patchedSrgJar.apply(extension.getMappingsProvider().patchedProvider);
+ FileSystemUtil.FileSystemDelegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false);
+ completer.onComplete(value -> system.close());
+
+ for (Path fsPath : (Iterable<? extends Path>) Files.walk(system.get().getPath("/"))::iterator) {
+ if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) {
+ completer.add(() -> {
+ byte[] bytes = Files.readAllBytes(fsPath);
+ new ClassReader(bytes).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ });
+ }
+ }
+ }
+
+ completer.complete();
+ Map<FieldMember, String> migratedFields = new HashMap<>();
+
+ try (BufferedReader reader = Files.newBufferedReader(rawTinyMappingsWithSrg)) {
+ TinyTree mappings = TinyMappingFactory.loadWithDetection(reader);
+ Map<String, String> srgToIntermediary = new HashMap<>();
+
+ for (ClassDef aClass : mappings.getClasses()) {
+ srgToIntermediary.put(aClass.getName("srg"), aClass.getName("intermediary"));
+ }
+
+ for (ClassDef classDef : mappings.getClasses()) {
+ String ownerSrg = classDef.getName("srg");
+ String ownerIntermediary = classDef.getName("intermediary");
+
+ for (FieldDef fieldDef : classDef.getFields()) {
+ String fieldSrg = fieldDef.getName("srg");
+ String descriptorSrg = fieldDef.getDescriptor("srg");
+
+ FieldMember member = new FieldMember(ownerSrg, fieldSrg);
+ String newDescriptor = fieldDescriptorMap.get(member);
+
+ if (newDescriptor != null && !newDescriptor.equals(descriptorSrg)) {
+ String fieldIntermediary = fieldDef.getName("intermediary");
+ String descriptorIntermediary = fieldDef.getDescriptor("intermediary");
+ String newDescriptorRemapped = DescriptorRemapper.remapDescriptor(newDescriptor,
+ clazz -> srgToIntermediary.getOrDefault(clazz, clazz));
+ migratedFields.put(new FieldMember(ownerIntermediary, fieldIntermediary), newDescriptorRemapped);
+ getProject().getLogger().info(ownerIntermediary + "#" + fieldIntermediary + ": " + descriptorIntermediary + " -> " + newDescriptorRemapped);
+ }
+ }
+ }
+ }
+
+ this.migratedFields.clear();
+ this.migratedFields.addAll(migratedFields.entrySet());
+ }
+
+ public static class FieldMember {
+ public String owner;
+ public String field;
+
+ public FieldMember(String owner, String field) {
+ this.owner = owner;
+ this.field = field;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ FieldMember that = (FieldMember) o;
+ return Objects.equals(owner, that.owner) && Objects.equals(field, that.field);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(owner, field);
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java
new file mode 100644
index 00000000..50f6f82c
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java
@@ -0,0 +1,87 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-2021 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.forge;
+
+import java.util.function.Consumer;
+
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+
+public class ForgeProvider extends DependencyProvider {
+ private ForgeVersion version = new ForgeVersion(null);
+
+ public ForgeProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ version = new ForgeVersion(dependency.getResolvedVersion());
+ addDependency(dependency.getDepString() + ":userdev", Constants.Configurations.FORGE_USERDEV);
+ addDependency(dependency.getDepString() + ":installer", Constants.Configurations.FORGE_INSTALLER);
+ }
+
+ public ForgeVersion getVersion() {
+ return version;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.FORGE;
+ }
+
+ public static final class ForgeVersion {
+ private final String minecraftVersion;
+ private final String forgeVersion;
+
+ public ForgeVersion(String combined) {
+ if (combined == null) {
+ this.minecraftVersion = "NO_VERSION";
+ this.forgeVersion = "NO_VERSION";
+ return;
+ }
+
+ int hyphenIndex = combined.indexOf('-');
+
+ if (hyphenIndex != -1) {
+ this.minecraftVersion = combined.substring(0, hyphenIndex);
+ this.forgeVersion = combined.substring(hyphenIndex + 1);
+ } else {
+ this.minecraftVersion = "NO_VERSION";
+ this.forgeVersion = combined;
+ }
+ }
+
+ public String getMinecraftVersion() {
+ return minecraftVersion;
+ }
+
+ public String getForgeVersion() {
+ return forgeVersion;
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java
new file mode 100644
index 00000000..5ad0555a
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java
@@ -0,0 +1,61 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-2021 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.forge;
+
+import java.io.File;
+import java.util.function.Consumer;
+
+import org.apache.commons.io.FileUtils;
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+
+public class ForgeUniversalProvider extends DependencyProvider {
+ private File forge;
+
+ public ForgeUniversalProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ forge = new File(getDirectories().getProjectPersistentCache(), "forge-" + dependency.getDependency().getVersion() + "-universal.jar");
+
+ if (!forge.exists() || isRefreshDeps()) {
+ File dep = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge"));
+ FileUtils.copyFile(dep, forge);
+ }
+ }
+
+ public File getForge() {
+ return forge;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.FORGE_UNIVERSAL;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java
new file mode 100644
index 00000000..26add71e
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java
@@ -0,0 +1,105 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-2021 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.forge;
+
+import java.io.File;
+import java.io.Reader;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.function.Consumer;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+
+public class ForgeUserdevProvider extends DependencyProvider {
+ private File userdevJar;
+
+ public ForgeUserdevProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ userdevJar = new File(getDirectories().getProjectPersistentCache(), "forge-" + dependency.getDependency().getVersion() + "-userdev.jar");
+
+ Path configJson = getDirectories()
+ .getProjectPersistentCache()
+ .toPath()
+ .resolve("forge-config-" + dependency.getDependency().getVersion() + ".json");
+
+ if (!userdevJar.exists() || Files.notExists(configJson) || isRefreshDeps()) {
+ File resolved = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge userdev"));
+ Files.copy(resolved.toPath(), userdevJar.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + resolved.toURI()), ImmutableMap.of("create", false))) {
+ Files.copy(fs.getPath("config.json"), configJson, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ JsonObject json;
+
+ try (Reader reader = Files.newBufferedReader(configJson)) {
+ json = new Gson().fromJson(reader, JsonObject.class);
+ }
+
+ 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);
+
+ for (JsonElement lib : json.get("libraries").getAsJsonArray()) {
+ if (lib.getAsString().startsWith("org.spongepowered:mixin:")) {
+ if (getExtension().isUseFabricMixin()) {
+ addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES);
+ continue;
+ }
+ }
+
+ addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES);
+ }
+
+ // TODO: Read launch configs from the JSON too
+ // TODO: Should I copy the patches from here as well?
+ // That'd require me to run the "MCP environment" fully up to merging.
+ }
+
+ public File getUserdevJar() {
+ return userdevJar;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.FORGE_USERDEV;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java
new file mode 100644
index 00000000..78375240
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java
@@ -0,0 +1,72 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-2021 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.forge;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.function.Consumer;
+
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+
+public class McpConfigProvider extends DependencyProvider {
+ private File mcp;
+
+ public McpConfigProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ init(dependency.getDependency().getVersion());
+
+ if (mcp.exists() && !isRefreshDeps()) {
+ return; // No work for us to do here
+ }
+
+ Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath();
+
+ if (!mcp.exists() || isRefreshDeps()) {
+ Files.copy(mcpZip, mcp.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ private void init(String version) {
+ mcp = new File(getDirectories().getUserCache(), "mcp-" + version + ".zip");
+ }
+
+ public File getMcp() {
+ return mcp;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.MCP_CONFIG;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java
new file mode 100644
index 00000000..94ed967c
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java
@@ -0,0 +1,694 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-2021 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.forge;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.stream.Stream;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteSource;
+import com.google.gson.JsonParser;
+import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer;
+import dev.architectury.tinyremapper.InputTag;
+import dev.architectury.tinyremapper.OutputConsumerPath;
+import dev.architectury.tinyremapper.TinyRemapper;
+import net.minecraftforge.binarypatcher.ConsoleTool;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.NullOutputStream;
+import org.gradle.api.Project;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.SourceSet;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.tree.ClassNode;
+import org.zeroturnaround.zip.ZipUtil;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
+import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.DependencyDownloader;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.ThreadingUtils;
+import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
+import net.fabricmc.loom.util.function.FsPathConsumer;
+import net.fabricmc.loom.util.srg.InnerClassRemapper;
+import net.fabricmc.loom.util.srg.SpecialSourceExecutor;
+import net.fabricmc.mapping.tree.TinyTree;
+
+public class MinecraftPatchedProvider extends DependencyProvider {
+ private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version";
+ private static final String CURRENT_LOOM_PATCH_VERSION = "4";
+ private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService";
+
+ // Step 1: Remap Minecraft to SRG (global)
+ private File minecraftClientSrgJar;
+ private File minecraftServerSrgJar;
+ // Step 2: Binary Patch (global)
+ private File minecraftClientPatchedSrgJar;
+ private File minecraftServerPatchedSrgJar;
+ // Step 3: Merge (global)
+ private File minecraftMergedPatchedSrgJar;
+ // Step 4: Access Transform (global or project)
+ private File minecraftMergedPatchedSrgAtJar;
+ // Step 5: Remap Patched AT & Forge to Official (global or project)
+ private File minecraftMergedPatchedJar;
+ private File forgeMergedJar;
+
+ private File projectAtHash;
+ private Set<File> projectAts = new HashSet<>();
+ private boolean atDirty = false;
+ private boolean filesDirty = false;
+
+ public MinecraftPatchedProvider(Project project) {
+ super(project);
+ }
+
+ public void initFiles() throws IOException {
+ filesDirty = false;
+ projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256");
+ projectAts = getExtension().getAccessTransformers();
+
+ if (projectAts.isEmpty()) {
+ SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main");
+
+ for (File srcDir : main.getResources().getSrcDirs()) {
+ File projectAt = new File(srcDir, "META-INF/accesstransformer.cfg");
+
+ if (projectAt.exists()) {
+ this.projectAts.add(projectAt);
+ break;
+ }
+ }
+ }
+
+ if (isRefreshDeps() || !projectAtHash.exists()) {
+ writeAtHash();
+ atDirty = !projectAts.isEmpty();
+ } else {
+ byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read();
+ byte[] current = getProjectAtsHash();
+ boolean mismatched = !Arrays.equals(current, expected);
+
+ if (mismatched) {
+ writeAtHash();
+ }
+
+ atDirty = mismatched;
+ }
+
+ MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider();
+ PatchProvider patchProvider = getExtension().getPatchProvider();
+ String minecraftVersion = minecraftProvider.minecraftVersion();
+ String patchId = "forge-" + patchProvider.forgeVersion;
+
+ if (getExtension().isUseFabricMixin()) {
+ patchId += "-fabric-mixin";
+ }
+
+ minecraftProvider.setJarSuffix(patchId);
+
+ File globalCache = getDirectories().getUserCache();
+ File cache = usesProjectCache() ? getDirectories().getProjectPersistentCache() : globalCache;
+ File globalDir = new File(globalCache, patchId);
+ File projectDir = new File(cache, patchId);
+ globalDir.mkdirs();
+ projectDir.mkdirs();
+
+ minecraftClientSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-srg.jar");
+ minecraftServerSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-srg.jar");
+ minecraftClientPatchedSrgJar = new File(globalDir, "client-srg-patched.jar");
+ minecraftServerPatchedSrgJar = new File(globalDir, "server-srg-patched.jar");
+ minecraftMergedPatchedSrgJar = new File(globalDir, "merged-srg-patched.jar");
+ forgeMergedJar = new File(globalDir, "forge-official.jar");
+ minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar");
+ minecraftMergedPatchedJar = new File(projectDir, "merged-patched.jar");
+
+ if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(Predicates.not(File::exists))
+ || !isPatchedJarUpToDate(minecraftMergedPatchedJar)) {
+ cleanAllCache();
+ } else if (atDirty || Stream.of(getProjectCache()).anyMatch(Predicates.not(File::exists))) {
+ cleanProjectCache();
+ }
+ }
+
+ private byte[] getProjectAtsHash() throws IOException {
+ if (projectAts.isEmpty()) return ByteSource.empty().hash(Hashing.sha256()).asBytes();
+ List<ByteSource> currentBytes = new ArrayList<>();
+
+ for (File projectAt : projectAts) {
+ currentBytes.add(com.google.common.io.Files.asByteSource(projectAt));
+ }
+
+ return ByteSource.concat(currentBytes).hash(Hashing.sha256()).asBytes();
+ }
+
+ public void cleanAllCache() {
+ for (File file : getGlobalCaches()) {
+ file.delete();
+ }
+
+ cleanProjectCache();
+ }
+
+ private File[] getGlobalCaches() {
+ return new File[] {
+ minecraftClientSrgJar,
+ minecraftServerSrgJar,
+ minecraftClientPatchedSrgJar,
+ minecraftServerPatchedSrgJar,
+ minecraftMergedPatchedSrgJar,
+ forgeMergedJar,
+ };
+ }
+
+ public void cleanProjectCache() {
+ for (File file : getProjectCache()) {
+ file.delete();
+ }
+ }
+
+ private File[] getProjectCache() {
+ return new File[] {
+ minecraftMergedPatchedSrgAtJar,
+ minecraftMergedPatchedJar
+ };
+ }
+
+ private boolean dirty;
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ initFiles();
+
+ if (atDirty) {
+ getProject().getLogger().lifecycle(":found dirty access transformers");
+ }
+
+ this.dirty = false;
+
+ if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) {
+ this.dirty = true;
+ // Remap official jars to MCPConfig remapped srg jars
+ createSrgJars(getProject().getLogger());
+ }
+
+ if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) {
+ this.dirty = true;
+ patchJars(getProject().getLogger());
+ }
+ }
+
+ public void finishProvide() throws Exception {
+ if (dirty || !minecraftMergedPatchedSrgJar.exists()) {
+ mergeJars(getProject().getLogger());
+ }
+
+ if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) {
+ this.dirty = true;
+ accessTransformForge(getProject().getLogger());
+ }
+
+ if (!forgeMergedJar.exists()) {
+ this.dirty = true;
+ }
+
+ Path input = minecraftMergedPatchedSrgAtJar.toPath();
+ if (dirty) {
+ remapPatchedJar(input, getProject().getLogger());
+ }
+
+ this.filesDirty = dirty;
+ this.dirty = false;
+ }
+
+ private TinyRemapper buildRemapper(Path input) throws IOException {
+ Path[] libraries = MinecraftMappedProvider.getRemapClasspath(getProject());
+ TinyTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg();
+ TinyRemapper remapper = TinyRemapper.newRemapper()
+ .logger(getProject().getLogger()::lifecycle)
+ .logUnknownInvokeDynamic(false)
+ .withMappings(TinyRemapperMappingsHelper.create(mappingsWithSrg, "srg", "official", true))
+ .withMappings(InnerClassRemapper.of(input, mappingsWithSrg, "srg", "official"))
+ .renameInvalidLocals(true)
+ .rebuildSourceFilenames(true)
+ .fixPackageAccess(true)
+ .build();
+
+ remapper.readClassPath(libraries);
+ remapper.prepareClasses();
+ return remapper;
+ }
+
+ private void writeAtHash() throws IOException {
+ try (FileOutputStream out = new FileOutputStream(projectAtHash)) {
+ out.write(getProjectAtsHash());
+ }
+ }
+
+ private void createSrgJars(Logger logger) throws Exception {
+ McpConfigProvider mcpProvider = getExtension().getMcpConfigProvider();
+
+ MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider();
+
+ String[] mappingsPath = {null};
+
+ if (!ZipUtil.handle(mcpProvider.getMcp(), "config.json", (in, zipEntry) -> {
+ mappingsPath[0] = JsonParser.parseReader(new InputStreamReader(in)).getAsJsonObject().get("data").getAsJsonObject().get("mappings").getAsString();
+ })) {
+ throw new IllegalStateException("Failed to find 'config.json' in " + mcpProvider.getMcp().getAbsolutePath() + "!");
+ }
+
+ Path[] tmpSrg = {null};
+
+ if (!ZipUtil.handle(mcpProvider.getMcp(), mappingsPath[0], (in, zipEntry) -> {
+ tmpSrg[0] = Files.createTempFile(null, null);
+
+ try (BufferedWriter writer = Files.newBufferedWriter(tmpSrg[0])) {
+ IOUtils.copy(in, writer, StandardCharsets.UTF_8);
+ }
+ })) {
+ throw new IllegalStateException("Failed to find mappings '" + mappingsPath[0] + "' in " + mcpProvider.getMcp().getAbsolutePath() + "!");
+ }
+
+ String atDependency = Constants.Dependencies.SPECIAL_SOURCE + Constants.Dependencies.Versions.SPECIAL_SOURCE + ":shaded";
+ // Do getFiles() to resolve it before multithreading it
+ FileCollection classpath = getProject().files(DependencyDownloader.download(getProject(), atDependency).getFiles());
+
+ ThreadingUtils.run(() -> {
+ Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), "client", classpath, minecraftProvider.minecraftClientJar.toPath(), tmpSrg[0]), minecraftClientSrgJar.toPath());
+ }, () -> {
+ Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), "server", classpath, minecraftProvider.minecraftServerJar.toPath(), tmpSrg[0]), minecraftServerSrgJar.toPath());
+ });
+ }
+
+ private void fixParameterAnnotation(File jarFile) throws Exception {
+ getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath());
+ Stopwatch stopwatch = Stopwatch.createStarted();
+
+ try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) {
+ ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter();
+
+ for (Path file : (Iterable<? extends Path>) Files.walk(fs.getPath("/"))::iterator) {
+ if (!file.toString().endsWith(".class")) continue;
+
+ completer.add(() -> {
+ byte[] bytes = Files.readAllBytes(file);
+ ClassReader reader = new ClassReader(bytes);
+ ClassNode node = new ClassNode();
+ ClassVisitor visitor = new ParameterAnnotationFixer(node, null);
+ reader.accept(visitor, 0);
+
+ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ node.accept(writer);
+ byte[] out = writer.toByteArray();
+
+ if (!Arrays.equals(bytes, out)) {
+ Files.delete(file);
+ Files.write(file, out);
+ }
+ });
+ }
+
+ completer.complete();
+ }
+
+ getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch);
+ }
+
+ private File getForgeJar() {
+ return getExtension().getForgeUniversalProvider().getForge();
+ }
+
+ private File getForgeUserdevJar() {
+ return getExtension().getForgeUserdevProvider().getUserdevJar();
+ }
+
+ private boolean isPatchedJarUpToDate(File jar) {
+ if (!jar.exists()) return false;
+
+ boolean[] upToDate = {false};
+
+ if (!ZipUtil.handle(jar, "META-INF/MANIFEST.MF", (in, zipEntry) -> {
+ Manifest manifest = new Manifest(in);
+ Attributes attributes = manifest.getMainAttributes();
+ String value = attributes.getValue(LOOM_PATCH_VERSION_KEY);
+
+ if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) {
+ upToDate[0] = true;
+ } else {
+ getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value);
+ }
+ })) {
+ return false;
+ }
+
+ return upToDate[0];
+ }
+
+ private void accessTransformForge(Logger logger) throws Exception {
+ List<File> toDelete = new ArrayList<>();
+ var atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + Constants.Dependencies.Versions.ACCESS_TRANSFORMERS;
+ var classpath = DependencyDownloader.download(getProject(), atDependency);
+
+ logger.lifecycle(":access transforming minecraft");
+
+ File input = minecraftMergedPatchedSrgJar;
+ File target = minecraftMergedPatchedSrgAtJar;
+ Files.deleteIfExists(target.toPath());
+
+ List<String> args = new ArrayList<>();
+ args.add("--inJar");
+ args.add(input.getAbsolutePath());
+ args.add("--outJar");
+ args.add(target.getAbsolutePath());
+
+ for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) {
+ try (FileSystemUtil.FileSystemDelegate fs = FileSystemUtil.getJarFileSystem(jar, false)) {
+ Path atPath = fs.get().getPath("META-INF/accesstransformer.cfg");
+
+ if (Files.exists(atPath)) {
+ File tmpFile = File.createTempFile("at-conf", ".cfg");
+ tmpFile.delete();
+ toDelete.add(tmpFile);
+ Files.copy(atPath, tmpFile.toPath());
+ args.add("--atFile");
+ args.add(tmpFile.getAbsolutePath());
+ }
+ }
+ }
+
+ if (usesProjectCache()) {
+ for (File projectAt : projectAts) {
+ args.add("--atFile");
+ args.add(projectAt.getAbsolutePath());
+ }
+ }
+
+ getProject().javaexec(spec -> {
+ spec.setMain("net.minecraftforge.accesstransformer.TransformerProcessor");
+ spec.setArgs(args);
+ spec.setClasspath(classpath);
+
+ // if running with INFO or DEBUG logging
+ if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) {
+ spec.setStandardOutput(System.out);
+ } else {
+ spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM);
+ spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM);
+ }
+ }).rethrowFailure().assertNormalExitValue();
+
+ for (File file : toDelete) {
+ file.delete();
+ }
+ }
+
+ public enum Environment {
+ CLIENT(provider -> provider.minecraftClientSrgJar,
+ provider -> provider.minecraftClientPatchedSrgJar
+ ),
+ SERVER(provider -> provider.minecraftServerSrgJar,
+ provider -> provider.minecraftServerPatchedSrgJar
+ );
+
+ final Function<MinecraftPatchedProvider, File> srgJar;
+ final Function<MinecraftPatchedProvider, File> patchedSrgJar;
+
+ Environment(Function<MinecraftPatchedProvider, File> srgJar,
+ Function<MinecraftPatchedProvider, File> patchedSrgJar) {
+ this.srgJar = srgJar;
+ this.patchedSrgJar = patchedSrgJar;
+ }
+
+ public String side() {
+ return name().toLowerCase(Locale.ROOT);
+ }
+ }
+
+ private void remapPatchedJar(Path input, Logger logger) throws Exception {
+ getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)");
+ Path mcOutput = minecraftMergedPatchedJar.toPath();
+ Path forgeOutput = forgeMergedJar.toPath();
+ Path forgeJar = getForgeJar().toPath();
+ Path forgeUserdevJar = getForgeUserdevJar().toPath();
+ Files.deleteIfExists(mcOutput);
+ Files.deleteIfExists(forgeOutput);
+ TinyRemapper remapper = buildRemapper(input);
+
+ try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build();
+ OutputConsumerPath outputConsumerForge = new OutputConsumerPath.Builder(forgeOutput).build()) {
+ outputConsumer.addNonClassFiles(input);
+
+ InputTag mcTag = remapper.createInputTag();
+ InputTag forgeTag = remapper.createInputTag();
+ List<CompletableFuture<?>> futures = new ArrayList<>();
+ futures.add(remapper.readInputsAsync(mcTag, input));
+ futures.add(remapper.readInputsAsync(forgeTag, forgeJar, forgeUserdevJar));
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+ remapper.apply(outputConsumer, mcTag);
+ remapper.apply(outputConsumerForge, forgeTag);
+ } finally {
+ remapper.finish();
+ }
+
+ applyLoomPatchVersion(mcOutput);
+ copyNonClassFiles(forgeJar.toFile(), forgeMergedJar);
+ copyUserdevFiles(forgeUserdevJar.toFile(), forgeMergedJar);
+ }
+
+ private void patchJars(Logger logger) throws IOException {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ logger.lifecycle(":patching jars");
+
+ PatchProvider patchProvider = getExtension().getPatchProvider();
+ patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches);
+ patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches);
+
+ ThreadingUtils.run(Environment.values(), environment -> {
+ copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this));
+ fixParameterAnnotation(environment.patchedSrgJar.apply(this));
+ });
+
+ logger.lifecycle(":patched jars in " + stopwatch.stop());
+ }
+
+ private void patchJars(File clean, File output, Path patches) throws IOException {
+ PrintStream previous = System.out;
+
+ try {
+ System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
+ } catch (SecurityException ignored) {
+ // Failed to replace logger filter, just ignore
+ }
+
+ ConsoleTool.main(new String[] {
+ "--clean", clean.getAbsolutePath(),
+ "--output", output.getAbsolutePath(),
+ "--apply", patches.toAbsolutePath().toString()
+ });
+
+ try {
+ System.setOut(previous);
+ } catch (SecurityException ignored) {
+ // Failed to replace logger filter, just ignore
+ }
+ }
+
+ private void mergeJars(Logger logger) throws IOException {
+ // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR.
+ // This will change if upstream Loom adds the possibility for separate projects/source sets per environment.
+ Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath());
+
+ logger.lifecycle(":copying resources");
+
+ // Copy resources
+ MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider();
+ copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedSrgJar);
+ copyNonClassFiles(minecraftProvider.minecraftServerJar, minecraftMergedPatchedSrgJar);
+ }
+
+ private void walkFileSystems(File source, File target, Predicate<Path> filter, Function<FileSystem, Iterable<Path>> toWalk, FsPathConsumer action)
+ throws IOException {
+ try (FileSystemUtil.FileSystemDelegate sourceFs = FileSystemUtil.getJarFileSystem(source, false);
+ FileSystemUtil.FileSystemDelegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) {
+ for (Path sourceDir : toWalk.apply(sourceFs.get())) {
+ Path dir = sourceDir.toAbsolutePath();
+ Files.walk(dir)
+ .filter(Files::isRegularFile)
+ .filter(filter)
+ .forEach(it -> {
+ boolean root = dir.getParent() == null;
+
+ try {
+ Path relativeSource = root ? it : dir.relativize(it);
+ Path targetPath = targetFs.get().getPath(relativeSource.toString());
+ action.accept(sourceFs.get(), targetFs.get(), it, targetPath);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
+ }
+ }
+ }
+
+ private void walkFileSystems(File source, File target, Predicate<Path> filter, FsPathConsumer action) throws IOException {
+ walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action);
+ }
+
+ private void copyAll(File source, File target) throws IOException {
+ walkFileSystems(source, target, it -> true, this::copyReplacing);
+ }
+
+ private void copyMissingClasses(File source, File target) throws IOException {
+ walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> {
+ if (Files.exists(targetPath)) return;
+ Path parent = targetPath.getParent();
+
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
+
+ Files.copy(sourcePath, targetPath);
+ });
+ }
+
+ private void copyNonClassFiles(File source, File target) throws IOException {
+ Predicate<Path> filter = file -> {
+ String s = file.toString();
+ return !s.endsWith(".class");
+ };
+
+ walkFileSystems(source, target, filter, this::copyReplacing);
+ }
+
+ private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException {
+ Path parent = targetPath.getParent();
+
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
+
+ Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ private void copyUserdevFiles(File source, File target) throws IOException {
+ // Removes the Forge name mapping service definition so that our own is used.
+ // If there are multiple name mapping services with the same "understanding" pair
+ // (source -> target namespace pair), modlauncher throws a fit and will crash.
+ // To use our YarnNamingService instead of MCPNamingService, we have to remove this file.
+ Predicate<Path> filter = file -> !file.toString().endsWith(".class") && !file.toString().equals(NAME_MAPPING_SERVICE_PATH);
+
+ walkFileSystems(source, target, filter, fs -> Collections.singleton(fs.getPath("inject")), (sourceFs, targetFs, sourcePath, targetPath) -> {
+ Path parent = targetPath.getParent();
+
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
+
+ Files.copy(sourcePath, targetPath);
+ });
+ }
+
+ public void applyLoomPatchVersion(Path target) throws IOException {
+ try (FileSystemUtil.FileSystemDelegate delegate = FileSystemUtil.getJarFileSystem(target, false)) {
+ Path manifestPath = delegate.get().getPath("META-INF/MANIFEST.MF");
+
+ Preconditions.checkArgument(Files.exists(manifestPath), "META-INF/MANIFEST.MF does not exist in patched srg jar!");
+ Manifest manifest = new Manifest();
+
+ if (Files.exists(manifestPath)) {
+ try (InputStream stream = Files.newInputStream(manifestPath)) {
+ manifest.read(stream);
+ manifest.getMainAttributes().putValue(LOOM_PATCH_VERSION_KEY, CURRENT_LOOM_PATCH_VERSION);
+ }
+ }
+
+ try (OutputStream stream = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE)) {
+ manifest.write(stream);
+ }
+ }
+ }
+
+ public File getMergedJar() {
+ return minecraftMergedPatchedJar;
+ }
+
+ public File getForgeMergedJar() {
+ return forgeMergedJar;
+ }
+
+ public boolean usesProjectCache() {
+ return !projectAts.isEmpty();
+ }
+
+ public boolean isAtDirty() {
+ return atDirty || filesDirty;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.MINECRAFT;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java
new file mode 100644
index 00000000..2d6d8f46
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java
@@ -0,0 +1,90 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-2021 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.forge;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.function.Consumer;
+
+import com.google.common.collect.ImmutableMap;
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+
+public class PatchProvider extends DependencyProvider {
+ public Path clientPatches;
+ public Path serverPatches;
+ public String forgeVersion;
+ public Path projectCacheFolder;
+
+ public PatchProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ init(dependency.getDependency().getVersion());
+
+ if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) {
+ getProject().getLogger().info(":extracting forge patches");
+
+ Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge installer")).toPath();
+
+ try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) {
+ Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ }
+
+ private void init(String forgeVersion) {
+ this.forgeVersion = forgeVersion;
+ projectCacheFolder = getDirectories().getProjectPersistentCache().toPath().resolve(forgeVersion);
+ clientPatches = projectCacheFolder.resolve("patches-client.lzma");
+ serverPatches = projectCacheFolder.resolve("patches-server.lzma");
+
+ try {
+ Files.createDirectories(projectCacheFolder);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public Path getProjectCacheFolder() {
+ return projectCacheFolder;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.FORGE_INSTALLER;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java
new file mode 100644
index 00000000..b4cf837a
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-2021 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.forge;
+
+import java.io.File;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.function.Consumer;
+
+import com.google.common.collect.ImmutableMap;
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+
+public class SrgProvider extends DependencyProvider {
+ private File srg;
+
+ public SrgProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ init(dependency.getDependency().getVersion());
+
+ if (srg.exists() && !isRefreshDeps()) {
+ return; // No work for us to do here
+ }
+
+ Path srgZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve srg")).toPath();
+
+ if (!srg.exists() || isRefreshDeps()) {
+ try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) {
+ Files.copy(fs.getPath("config", "joined.tsrg"), srg.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ }
+
+ private void init(String version) {
+ srg = new File(getDirectories().getUserCache(), "srg-" + version + ".tsrg");
+ }
+
+ public File getSrg() {
+ return srg;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.SRG;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilder.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilder.java
index eae20663..2e5ed76a 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilder.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilder.java
@@ -29,16 +29,25 @@ import java.util.LinkedList;
import java.util.List;
import org.gradle.api.Action;
+import org.jetbrains.annotations.Nullable;
+import net.fabricmc.loom.api.LoomGradleExtensionAPI;
+import net.fabricmc.loom.configuration.providers.mappings.crane.CraneMappingsSpec;
import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingsSpec;
import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsSpec;
import net.fabricmc.loom.configuration.providers.mappings.parchment.ParchmentMappingsSpecBuilder;
public class LayeredMappingSpecBuilder {
private final List<MappingsSpec<?>> layers = new LinkedList<>();
+ @Nullable
+ private final LoomGradleExtensionAPI extension;
+
+ public LayeredMappingSpecBuilder(@Nullable LoomGradleExtensionAPI extension) {
+ this.extension = extension;
+ }
public LayeredMappingSpecBuilder officialMojangMappings() {
- layers.add(new MojangMappingsSpec());
+ layers.add(new MojangMappingsSpec(() -> extension != null && extension.isSilentMojangMappingsLicenseEnabled()));
return this;
}
@@ -54,6 +63,11 @@ public class LayeredMappingSpecBuilder {
return this;
}
+ public LayeredMappingSpecBuilder crane(String mavenNotation) {
+ layers.add(new CraneMappingsSpec(mavenNotation));
+ return this;
+ }
+
public LayeredMappingSpec build() {
List<MappingsSpec<?>> builtLayers = new LinkedList<>();
// Intermediary is always the base layer
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java
index 903d347a..f15f6eca 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java
@@ -28,20 +28,28 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.function.Consumer;
import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
import com.google.common.net.UrlEscapers;
import com.google.gson.JsonObject;
import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
import org.zeroturnaround.zip.FileSource;
import org.zeroturnaround.zip.ZipEntrySource;
import org.zeroturnaround.zip.ZipUtil;
@@ -53,25 +61,39 @@ import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
+import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider;
+import net.fabricmc.loom.configuration.providers.forge.SrgProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.DownloadUtil;
+import net.fabricmc.loom.util.srg.MCPReader;
+import net.fabricmc.loom.util.srg.SrgMerger;
+import net.fabricmc.loom.util.srg.SrgNamedWriter;
+import net.fabricmc.mapping.reader.v2.TinyMetadata;
import net.fabricmc.mapping.reader.v2.TinyV2Factory;
import net.fabricmc.mapping.tree.TinyTree;
+import net.fabricmc.mappingio.adapter.MappingNsCompleter;
+import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
+import net.fabricmc.mappingio.format.Tiny2Reader;
+import net.fabricmc.mappingio.format.Tiny2Writer;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.stitch.Command;
import net.fabricmc.stitch.commands.CommandProposeFieldNames;
-import net.fabricmc.stitch.commands.tinyv2.CommandMergeTinyV2;
-import net.fabricmc.stitch.commands.tinyv2.CommandReorderTinyV2;
+import net.fabricmc.stitch.commands.tinyv2.TinyFile;
+import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer;
public class MappingsProviderImpl extends DependencyProvider implements MappingsProvider {
public MinecraftMappedProvider mappedProvider;
+ public MinecraftPatchedProvider patchedProvider;
public String mappingsName;
public String minecraftVersion;
public String mappingsVersion;
+ public String removeSuffix;
- private final Path mappingsDir;
+ protected final Path mappingsDir;
+ protected Path mappingsVersionedDir;
private final Path mappingsStepsDir;
private Path intermediaryTiny;
private boolean hasRefreshed = false;
@@ -79,7 +101,12 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
private Path baseTinyMappings;
// The mappings we use in practice
public File tinyMappings;
+ // tinyMappings wrapped in a jar
public File tinyMappingsJar;
+ public Path tinyMappingsWithSrg;
+ public File mixinTinyMappingsWithSrg; // FORGE: The mixin mappings have srg names in intermediary.
+ public File srgToNamedSrg; // FORGE: srg to named in srg file format
+
private File unpickDefinitionsFile;
private boolean hasUnpickDefinitions;
private UnpickMetadata unpickMetadata;
@@ -90,6 +117,28 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
mappingsStepsDir = mappingsDir.resolve("steps");
}
+ public Path getMappingsVersionedDir() throws IOException {
+ if (mappingsVersionedDir == null) {
+ mappingsVersionedDir = mappingsDir.resolve(getExtension().getMinecraftProvider().minecraftVersion());
+
+ if (!Files.exists(mappingsVersionedDir)) {
+ Files.createDirectories(mappingsVersionedDir);
+ }
+ }
+
+ return mappingsVersionedDir;
+ }
+
+ public Path getMappedVersionedDir(String name) throws IOException {
+ Path dir = getMappingsVersionedDir().resolve(name);
+
+ if (!Files.exists(dir)) {
+ Files.createDirectories(dir);
+ }
+
+ return dir;
+ }
+
public void clean() throws IOException {
FileUtils.deleteDirectory(mappingsDir.toFile());
}
@@ -98,6 +147,14 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
return MappingsCache.INSTANCE.get(tinyMappings.toPath());
}
+ public TinyTree getMappingsWithSrg() throws IOException {
+ if (getExtension().shouldGenerateSrgTiny()) {
+ return MappingsCache.INSTANCE.get(tinyMappingsWithSrg);
+ }
+
+ throw new UnsupportedOperationException("Not running with Forge support / Tiny srg support.");
+ }
+
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
MinecraftProviderImpl minecraftProvider = getDependencyManager().getProvider(MinecraftProviderImpl.class);
@@ -112,6 +169,13 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
boolean isV2;
+ if (isMCP(mappingsJar.toPath())) {
+ File old = mappingsJar;
+ mappingsJar = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".zip") + "-" + minecraftVersion + ".jar").toFile();
+ FileUtils.copyFile(old, mappingsJar);
+ mappingsName += "-" + minecraftVersion;
+ }
+
// Only do this for official yarn, there isn't really a way we can get the mc version for all mappings
if (dependency.getDependency().getGroup() != null && dependency.getDependency().getGroup().equals("net.fabricmc") && dependency.getDependency().getName().equals("yarn") && dependency.getDependency().getVersion() != null) {
String yarnVersion = dependency.getDependency().getVersion();
@@ -123,11 +187,12 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
// We can save reading the zip file + header by checking the file name
- isV2 = mappingsJar.getName().endsWith("-v2.jar");
+ isV2 = mappingsJar.getName().endsWith("-v2.jar") || mappingsJar.getName().endsWith("-mergedv2.jar");
} else {
isV2 = doesJarContainV2Mappings(mappingsJar.toPath());
}
+ this.removeSuffix = StringUtils.removeSuffix(mappingsJar.getName(), ".jar");
this.mappingsVersion = version + (isV2 ? "-v2" : "");
initFiles();
@@ -146,18 +211,36 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
jarClassifier = jarClassifier + depStringSplit[3];
}
- tinyMappings = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + ".tiny").toFile();
- unpickDefinitionsFile = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + ".unpick").toFile();
- tinyMappingsJar = new File(getDirectories().getUserCache(), mappingsJar.getName().replace(".jar", "-" + jarClassifier + ".jar"));
+ Path mappedVersionedDir = getMappedVersionedDir(removeSuffix);
+ tinyMappings = mappedVersionedDir.resolve("mappings.tiny").toFile();
+ unpickDefinitionsFile = mappedVersionedDir.resolve("definitions.unpick").toFile();
+ tinyMappingsJar = new File(getDirectories().getUserCache(), removeSuffix + "-" + jarClassifier + ".jar");
+ tinyMappingsWithSrg = mappedVersionedDir.resolve("mappings-srg.tiny");
+ mixinTinyMappingsWithSrg = mappedVersionedDir.resolve("mappings-mixin-srg.tiny").toFile();
+ srgToNamedSrg = mappedVersionedDir.resolve("mappings-srg-named.srg").toFile();
if (!tinyMappings.exists() || isRefreshDeps()) {
- storeMappings(getProject(), minecraftProvider, mappingsJar.toPath());
+ storeMappings(getProject(), minecraftProvider, mappingsJar.toPath(), postPopulationScheduler);
} else {
try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) {
extractUnpickDefinitions(fileSystem, unpickDefinitionsFile.toPath());
}
}
+ if (getExtension().isForge()) {
+ patchedProvider = new MinecraftPatchedProvider(getProject());
+ patchedProvider.provide(dependency, postPopulationScheduler);
+ }
+
+ manipulateMappings(mappingsJar.toPath());
+
+ if (getExtension().shouldGenerateSrgTiny()) {
+ if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) {
+ // Merge tiny mappings with srg
+ SrgMerger.mergeSrg(getExtension().getSrgProvider().getSrg().toPath(), tinyMappings.toPath(), tinyMappingsWithSrg, true);
+ }
+ }
+
if (!tinyMappingsJar.exists() || isRefreshDeps()) {
ZipUtil.pack(new ZipEntrySource[] {new FileSource("mappings/mappings.tiny", tinyMappings)}, tinyMappingsJar);
}
@@ -173,6 +256,23 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
populateUnpickClasspath();
}
+ if (getExtension().isForge()) {
+ if (!getExtension().shouldGenerateSrgTiny()) {
+ throw new IllegalStateException("We have to generate srg tiny in a forge environment!");
+ }
+
+ if (!mixinTinyMappingsWithSrg.exists() || isRefreshDeps()) {
+ List<String> lines = new ArrayList<>(Files.readAllLines(tinyMappingsWithSrg));
+ lines.set(0, lines.get(0).replace("intermediary", "yraidemretni").replace("srg", "intermediary"));
+ Files.deleteIfExists(mixinTinyMappingsWithSrg.toPath());
+ Files.write(mixinTinyMappingsWithSrg.toPath(), lines);
+ }
+
+ if (!srgToNamedSrg.exists() || isRefreshDeps()) {
+ SrgNamedWriter.writeTo(getProject().getLogger(), srgToNamedSrg.toPath(), getMappingsWithSrg(), "srg", "named");
+ }
+ }
+
addDependency(tinyMappingsJar, Constants.Configurations.MAPPINGS_FINAL);
LoomGradleExtension extension = getExtension();
@@ -187,7 +287,11 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
extension.setJarProcessorManager(processorManager);
processorManager.setupProcessors();
- if (processorManager.active()) {
+ if (extension.isForge()) {
+ patchedProvider.finishProvide();
+ }
+
+ if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) {
mappedProvider = new MinecraftProcessedProvider(getProject(), processorManager);
getProject().getLogger().lifecycle("Using project based jar storage");
} else {
@@ -198,15 +302,28 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
mappedProvider.provide(dependency, postPopulationScheduler);
}
- private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar) throws IOException {
+ public void manipulateMappings(Path mappingsJar) throws IOException { }
+
+ private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar, Consumer<Runnable> postPopulationScheduler)
+ throws Exception {
project.getLogger().info(":extracting " + yarnJar.getFileName());
+ if (isMCP(yarnJar)) {
+ readAndMergeMCP(yarnJar, postPopulationScheduler);
+ return;
+ }
+
try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) {
extractMappings(fileSystem, baseTinyMappings);
extractUnpickDefinitions(fileSystem, unpickDefinitionsFile.toPath());
}
- if (baseMappingsAreV2()) {
+ if (baseMappingsAreMergedV2()) {
+ // Architectury Loom Patch
+ // If a merged tiny v2 mappings file is provided
+ // Skip merging, should save a lot of time
+ Files.copy(baseTinyMappings, tinyMappings.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } else if (baseMappingsAreV2()) {
// These are unmerged v2 mappings
mergeAndSaveMappings(project, yarnJar);
} else {
@@ -220,16 +337,53 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
}
+ private void readAndMergeMCP(Path mcpJar, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ Path intermediaryTinyPath = getIntermediaryTiny();
+ SrgProvider provider = getExtension().getSrgProvider();
+
+ if (provider == null) {
+ if (!getExtension().shouldGenerateSrgTiny()) {
+ Configuration srg = getProject().getConfigurations().maybeCreate(Constants.Configurations.SRG);
+ srg.setTransitive(false);
+ }
+
+ provider = new SrgProvider(getProject());
+ getProject().getDependencies().add(provider.getTargetConfig(), "de.oceanlabs.mcp:mcp_config:" + minecraftVersion);
+ Configuration configuration = getProject().getConfigurations().getByName(provider.getTargetConfig());
+ provider.provide(DependencyInfo.create(getProject(), configuration.getDependencies().iterator().next(), configuration), postPopulationScheduler);
+ }
+
+ Path srgPath = provider.getSrg().toPath();
+
+ TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar);
+ TinyV2Writer.write(file, tinyMappings.toPath());
+ }
+
+ private boolean isMCP(Path path) throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) {
+ return Files.exists(fs.getPath("fields.csv")) && Files.exists(fs.getPath("methods.csv"));
+ }
+ }
+
private boolean baseMappingsAreV2() throws IOException {
try (BufferedReader reader = Files.newBufferedReader(baseTinyMappings)) {
TinyV2Factory.readMetadata(reader);
return true;
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException | NoSuchFileException e) {
// TODO: just check the mappings version when Parser supports V1 in readMetadata()
return false;
}
}
+ private boolean baseMappingsAreMergedV2() throws IOException {
+ try (BufferedReader reader = Files.newBufferedReader(baseTinyMappings)) {
+ TinyMetadata metadata = TinyV2Factory.readMetadata(reader);
+ return metadata.getNamespaces().containsAll(Arrays.asList("named", "intermediary", "official"));
+ } catch (IllegalArgumentException | NoSuchFileException e) {
+ return false;
+ }
+ }
+
private boolean doesJarContainV2Mappings(Path path) throws IOException {
try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) {
try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) {
@@ -295,34 +449,30 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
extractMappings(unmergedYarnJarFs, unmergedYarn);
}
- Path invertedIntermediary = Paths.get(mappingsStepsDir.toString(), "inverted-intermediary.tiny");
- reorderMappings(getIntermediaryTiny(), invertedIntermediary, "intermediary", "official");
- Path unorderedMergedMappings = Paths.get(mappingsStepsDir.toString(), "unordered-merged.tiny");
- project.getLogger().info(":merging");
- mergeMappings(invertedIntermediary, unmergedYarn, unorderedMergedMappings);
- reorderMappings(unorderedMergedMappings, tinyMappings.toPath(), "official", "intermediary", "named");
- }
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ project.getLogger().info(":merging mappings");
+ MemoryMappingTree tree = new MemoryMappingTree();
+ MappingNsCompleter nsCompleter = new MappingNsCompleter(tree, Collections.singletonMap(MappingNamespace.NAMED.stringValue(), MappingNamespace.INTERMEDIARY.stringValue()), true);
- private void reorderMappings(Path oldMappings, Path newMappings, String... newOrder) {
- Command command = new CommandReorderTinyV2();
- String[] args = new String[2 + newOrder.length];
- args[0] = oldMappings.toAbsolutePath().toString();
- args[1] = newMappings.toAbsolutePath().toString();
- System.arraycopy(newOrder, 0, args, 2, newOrder.length);
- runCommand(command, args);
- }
+ try (BufferedReader reader = Files.newBufferedReader(getIntermediaryTiny(), StandardCharsets.UTF_8)) {
+ Tiny2Reader.read(reader, nsCompleter);
+ }
- private void mergeMappings(Path intermediaryMappings, Path yarnMappings, Path newMergedMappings) {
- try {
- Command command = new CommandMergeTinyV2();
- runCommand(command, intermediaryMappings.toAbsolutePath().toString(),
- yarnMappings.toAbsolutePath().toString(),
- newMergedMappings.toAbsolutePath().toString(),
- "intermediary", "official");
- } catch (Exception e) {
- throw new RuntimeException("Could not merge mappings from " + intermediaryMappings.toString()
- + " with mappings from " + yarnMappings, e);
+ MemoryMappingTree tempTree = new MemoryMappingTree();
+
+ MappingSourceNsSwitch sourceNsSwitch = new MappingSourceNsSwitch(tempTree, MappingNamespace.OFFICIAL.stringValue());
+ tree.accept(sourceNsSwitch);
+ tree = tempTree;
+
+ try (BufferedReader reader = Files.newBufferedReader(unmergedYarn, StandardCharsets.UTF_8)) {
+ Tiny2Reader.read(reader, tree);
+ }
+
+ try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(tinyMappings.toPath(), StandardCharsets.UTF_8), false)) {
+ tree.accept(writer);
}
+
+ project.getLogger().info(":merged mappings in " + stopwatch.stop());
}
private void suggestFieldNames(MinecraftProviderImpl minecraftProvider, Path oldMappings, Path newMappings) {
@@ -340,8 +490,12 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
}
- private void initFiles() {
- baseTinyMappings = mappingsDir.resolve(mappingsName + "-tiny-" + minecraftVersion + "-" + mappingsVersion + "-base");
+ private void initFiles() throws IOException {
+ baseTinyMappings = getMappedVersionedDir(removeSuffix).resolve("mappings-base.tiny");
+
+ if (Files.exists(mappingsStepsDir)) {
+ Files.walkFileTree(mappingsStepsDir, new DeletingFileVisitor());
+ }
}
public void cleanFiles() {
@@ -383,13 +537,18 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
intermediaryTiny = mappingsDir.resolve(String.format("intermediary-%s-v2.tiny", minecraftVersion));
- if (!Files.exists(intermediaryTiny) || (isRefreshDeps() && !hasRefreshed)) {
+ if (isRefreshDeps() && !hasRefreshed) {
+ Files.deleteIfExists(intermediaryTiny);
+ }
+
+ if (!Files.exists(intermediaryTiny)) {
hasRefreshed = true;
+ intermediaryTiny = getMappingsVersionedDir().resolve("intermediary-v2.tiny");
// Download and extract intermediary
String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(minecraftVersion);
String intermediaryArtifactUrl = getExtension().getIntermediaryUrl(encodedMinecraftVersion);
- Path intermediaryJar = mappingsDir.resolve("v2-intermediary-" + minecraftVersion + ".jar");
+ Path intermediaryJar = getMappingsVersionedDir().resolve("intermediary-v2.jar");
DownloadUtil.downloadIfChanged(new URL(intermediaryArtifactUrl), intermediaryJar.toFile(), getProject().getLogger());
extractIntermediary(intermediaryJar, intermediaryTiny);
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java
new file mode 100644
index 00000000..620532f0
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java
@@ -0,0 +1,116 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2021 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.mappings.crane;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Collections;
+
+import dev.architectury.mappingslayers.api.mutable.MutableClassDef;
+import dev.architectury.mappingslayers.api.mutable.MutableFieldDef;
+import dev.architectury.mappingslayers.api.mutable.MutableMethodDef;
+import dev.architectury.mappingslayers.api.mutable.MutableParameterDef;
+import dev.architectury.mappingslayers.api.mutable.MutableTinyTree;
+import dev.architectury.mappingslayers.api.utils.MappingsUtils;
+import org.apache.commons.io.IOUtils;
+
+import net.fabricmc.loom.configuration.providers.mappings.MappingLayer;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.mappingio.MappedElementKind;
+import net.fabricmc.mappingio.MappingVisitor;
+
+public record CraneMappingLayer(File craneJar) implements MappingLayer {
+ private static final String TINY_FILE_NAME = "crane.tiny";
+
+ @Override
+ public void visit(MappingVisitor visitor) throws IOException {
+ try (FileSystemUtil.FileSystemDelegate fs = FileSystemUtil.getJarFileSystem(craneJar().toPath(), false)) {
+ try (BufferedReader reader = Files.newBufferedReader(fs.get().getPath(TINY_FILE_NAME), StandardCharsets.UTF_8)) {
+ // can't use this, it requires 2 namespaces
+ // Tiny2Reader.read(reader, mappingVisitor);
+ MutableTinyTree tree = MappingsUtils.deserializeFromString(IOUtils.toString(reader));
+
+ do {
+ if (visitor.visitHeader()) {
+ visitor.visitNamespaces(tree.getMetadata().getNamespaces().get(0), Collections.emptyList());
+ }
+
+ if (visitor.visitContent()) {
+ for (MutableClassDef classDef : tree.getClassesMutable()) {
+ if (visitor.visitClass(classDef.getName(0))) {
+ if (!visitor.visitElementContent(MappedElementKind.CLASS)) {
+ return;
+ }
+
+ for (MutableFieldDef fieldDef : classDef.getFieldsMutable()) {
+ if (visitor.visitField(fieldDef.getName(0), fieldDef.getDescriptor(0))) {
+ if (!visitor.visitElementContent(MappedElementKind.FIELD)) {
+ return;
+ }
+
+ if (fieldDef.getComment() != null) {
+ visitor.visitComment(MappedElementKind.FIELD, fieldDef.getComment());
+ }
+ }
+ }
+
+ for (MutableMethodDef methodDef : classDef.getMethodsMutable()) {
+ if (visitor.visitMethod(methodDef.getName(0), methodDef.getDescriptor(0))) {
+ if (!visitor.visitElementContent(MappedElementKind.METHOD)) {
+ return;
+ }
+
+ for (MutableParameterDef parameterDef : methodDef.getParametersMutable()) {
+ if (visitor.visitMethodArg(parameterDef.getLocalVariableIndex(), parameterDef.getLocalVariableIndex(), parameterDef.getName(0))) {
+ if (!visitor.visitElementContent(MappedElementKind.METHOD_ARG)) {
+ return;
+ }
+
+ if (parameterDef.getComment() != null) {
+ visitor.visitComment(MappedElementKind.METHOD_ARG, parameterDef.getComment());
+ }
+ }
+ }
+
+ if (methodDef.getComment() != null) {
+ visitor.visitComment(MappedElementKind.METHOD, methodDef.getComment());
+ }
+ }
+ }
+
+ if (classDef.getComment() != null) {
+ visitor.visitComment(MappedElementKind.FIELD, classDef.getComment());
+ }
+ }
+ }
+ }
+ } while (!visitor.visitEnd());
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java
new file mode 100644
index 00000000..f4ade2cc
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2021 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.mappings.crane;
+
+import net.fabricmc.loom.configuration.providers.mappings.MappingContext;
+import net.fabricmc.loom.configuration.providers.mappings.MappingsSpec;
+
+public record CraneMappingsSpec(String mavenNotation) implements MappingsSpec<CraneMappingLayer> {
+ @Override
+ public CraneMappingLayer createLayer(MappingContext context) {
+ return new CraneMappingLayer(context.mavenFile(mavenNotation()));
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java
index 0ca6414f..abfc3685 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java
@@ -30,6 +30,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections;
+import java.util.function.Supplier;
import net.fabricmc.loom.configuration.providers.mappings.MappingLayer;
import net.fabricmc.loom.configuration.providers.mappings.MappingNamespace;
@@ -37,7 +38,7 @@ import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.format.Tiny2Reader;
-public record IntermediaryMappingLayer(File tinyFile) implements MappingLayer {
+public record IntermediaryMappingLayer(Supplier<File> tinyFile) implements MappingLayer {
@Override
public MappingNamespace getSourceNamespace() {
return MappingNamespace.OFFICIAL;
@@ -48,7 +49,7 @@ public record IntermediaryMappingLayer(File tinyFile) implements MappingLayer {
// Populate named with intermediary and add Add a "named" namespace
MappingNsCompleter nsCompleter = new MappingNsCompleter(mappingVisitor, Collections.singletonMap(MappingNamespace.NAMED.stringValue(), MappingNamespace.INTERMEDIARY.stringValue()), true);
- try (BufferedReader reader = Files.newBufferedReader(tinyFile().toPath(), StandardCharsets.UTF_8)) {
+ try (BufferedReader reader = Files.newBufferedReader(tinyFile().get().toPath(), StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, nsCompleter);
}
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java
index 866ad9d7..4daf64f5 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java
@@ -30,6 +30,6 @@ import net.fabricmc.loom.configuration.providers.mappings.MappingsSpec;
public record IntermediaryMappingsSpec() implements MappingsSpec<IntermediaryMappingLayer> {
@Override
public IntermediaryMappingLayer createLayer(MappingContext context) {
- return new IntermediaryMappingLayer(context.mappingsProvider().intermediaryTinyFile());
+ return new IntermediaryMappingLayer(context.mappingsProvider()::intermediaryTinyFile);
}
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java
index fe96ef93..4b4d407b 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java
@@ -48,7 +48,8 @@ public record MojangMappingLayer(String minecraftVersion,
MinecraftVersionMeta.Download clientDownload,
MinecraftVersionMeta.Download serverDownload,
File workingDir,
- Logger logger) implements MappingLayer {
+ Logger logger,
+ MojangMappingsSpec.SilenceLicenseOption silenceLicense) implements MappingLayer {
@Override
public void visit(MappingVisitor mappingVisitor) throws IOException {
var clientMappings = new File(workingDir(), "%s.client.txt".formatted(minecraftVersion));
@@ -56,7 +57,9 @@ public record MojangMappingLayer(String minecraftVersion,
download(clientMappings, serverMappings);
- printMappingsLicense(clientMappings.toPath());
+ if (!silenceLicense.isSilent()) {
+ printMappingsLicense(clientMappings.toPath());
+ }
// Make official the source namespace
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(mappingVisitor, MappingNamespace.OFFICIAL.stringValue());
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java
index 8af54e53..f304530f 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java
@@ -28,11 +28,47 @@ import net.fabricmc.loom.configuration.providers.mappings.MappingContext;
import net.fabricmc.loom.configuration.providers.mappings.MappingsSpec;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
-public record MojangMappingsSpec() implements MappingsSpec<MojangMappingLayer> {
+public record MojangMappingsSpec(SilenceLicenseOption silenceLicense) implements MappingsSpec<MojangMappingLayer> {
// Keys in dependency manifest
private static final String MANIFEST_CLIENT_MAPPINGS = "client_mappings";
private static final String MANIFEST_SERVER_MAPPINGS = "server_mappings";
+ public MojangMappingsSpec(SilenceLicenseSupplier supplier) {
+ this(new SilenceLicenseOption(supplier));
+ }
+
+ public MojangMappingsSpec() {
+ this(() -> false);
+ }
+
+ @FunctionalInterface
+ public interface SilenceLicenseSupplier {
+ boolean isSilent();
+ }
+
+ public record SilenceLicenseOption(SilenceLicenseSupplier supplier) {
+ public boolean isSilent() {
+ return supplier.isSilent();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SilenceLicenseOption that)) return false;
+ return isSilent() == that.isSilent();
+ }
+
+ @Override
+ public int hashCode() {
+ return Boolean.hashCode(isSilent());
+ }
+
+ @Override
+ public String toString() {
+ return isSilent() + "";
+ }
+ }
+
@Override
public MojangMappingLayer createLayer(MappingContext context) {
MinecraftVersionMeta versionInfo = context.minecraftProvider().getVersionInfo();
@@ -46,7 +82,8 @@ public record MojangMappingsSpec() implements MappingsSpec<MojangMappingLayer> {
versionInfo.download(MANIFEST_CLIENT_MAPPINGS),
versionInfo.download(MANIFEST_SERVER_MAPPINGS),
context.workingDirectory("mojang"),
- context.getLogger()
+ context.getLogger(),
+ silenceLicense()
);
}
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java
index 1892a324..b4bc6c52 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java
@@ -28,30 +28,55 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
+import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
+import dev.architectury.tinyremapper.IMappingProvider;
+import dev.architectury.tinyremapper.InputTag;
+import dev.architectury.tinyremapper.NonClassCopyMode;
+import dev.architectury.tinyremapper.OutputConsumerPath;
+import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
+import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.configuration.DependencyProvider;
import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
+import net.fabricmc.loom.configuration.providers.minecraft.tr.OutputRemappingHandler;
+import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper;
import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.DownloadUtil;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.OperatingSystem;
+import net.fabricmc.loom.util.ThreadingUtils;
import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
-import net.fabricmc.tinyremapper.OutputConsumerPath;
-import net.fabricmc.tinyremapper.TinyRemapper;
+import net.fabricmc.loom.util.srg.AtRemapper;
+import net.fabricmc.loom.util.srg.CoreModClassRemapper;
+import net.fabricmc.loom.util.srg.InnerClassRemapper;
+import net.fabricmc.mapping.tree.TinyTree;
public class MinecraftMappedProvider extends DependencyProvider {
- private static final Map<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>()
+ public static final Map<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>()
.put("javax/annotation/Nullable", "org/jetbrains/annotations/Nullable")
.put("javax/annotation/Nonnull", "org/jetbrains/annotations/NotNull")
.put("javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable")
.build();
+ private File inputJar;
+ private File inputForgeJar;
private File minecraftMappedJar;
private File minecraftIntermediaryJar;
+ private File minecraftSrgJar;
+ private File forgeMappedJar;
+ private File forgeIntermediaryJar;
+ private File forgeSrgJar;
private MinecraftProviderImpl minecraftProvider;
@@ -65,11 +90,22 @@ public class MinecraftMappedProvider extends DependencyProvider {
throw new RuntimeException("mappings file not found");
}
- if (!getExtension().getMinecraftProvider().getMergedJar().exists()) {
+ if (!inputJar.exists()) {
throw new RuntimeException("input merged jar not found");
}
- if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || isRefreshDeps()) {
+ boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty();
+ boolean needToRemap = false;
+
+ if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || (getExtension().isForge() && !getSrgJar().exists()) || isRefreshDeps() || isForgeAtDirty) {
+ needToRemap = true;
+ }
+
+ if (getExtension().isForge() && (!getForgeMappedJar().exists() || !getForgeIntermediaryJar().exists() || !getForgeSrgJar().exists() || isRefreshDeps() || isForgeAtDirty)) {
+ needToRemap = true;
+ }
+
+ if (needToRemap) {
if (minecraftMappedJar.exists()) {
minecraftMappedJar.delete();
}
@@ -80,12 +116,37 @@ public class MinecraftMappedProvider extends DependencyProvider {
minecraftIntermediaryJar.delete();
}
+ if (getExtension().isForge() && minecraftSrgJar.exists()) {
+ minecraftSrgJar.delete();
+ }
+
+ if (getExtension().isForge()) {
+ if (getForgeMappedJar().exists()) {
+ getForgeMappedJar().delete();
+ }
+
+ getForgeMappedJar().getParentFile().mkdirs();
+ getForgeIntermediaryJar().delete();
+ getForgeSrgJar().delete();
+ }
+
try {
- mapMinecraftJar();
+ TinyRemapper[] remapperArray = new TinyRemapper[] {null};
+ mapMinecraftJar(remapperArray);
+ remapperArray[0].finish();
} catch (Throwable t) {
// Cleanup some some things that may be in a bad state now
- minecraftMappedJar.delete();
- minecraftIntermediaryJar.delete();
+ DownloadUtil.delete(minecraftMappedJar);
+ DownloadUtil.delete(minecraftIntermediaryJar);
+ getExtension().getMinecraftProvider().deleteFiles();
+
+ if (getExtension().isForge()) {
+ DownloadUtil.delete(minecraftSrgJar);
+ DownloadUtil.delete(forgeMappedJar);
+ DownloadUtil.delete(forgeSrgJar);
+ DownloadUtil.delete(forgeIntermediaryJar);
+ }
+
getExtension().getMappingsProvider().cleanFiles();
throw new RuntimeException("Failed to remap minecraft", t);
}
@@ -96,50 +157,191 @@ public class MinecraftMappedProvider extends DependencyProvider {
}
addDependencies(dependency, postPopulationScheduler);
+
+ if (getExtension().isForge()) {
+ getProject().getRepositories().flatDir(repository -> repository.dir(new File(getJarDirectory(getDirectories().getUserCache(), "mapped"), "forge")));
+
+ getProject().getDependencies().add(Constants.Configurations.FORGE_NAMED,
+ getProject().getDependencies().module("net.minecraftforge-loom:forge:" + getJarVersionString("mapped")));
+
+ getProject().afterEvaluate(project -> {
+ if (!OperatingSystem.isCIBuild()) {
+ try {
+ ForgeSourcesRemapper.addBaseForgeSources(project);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
}
- private void mapMinecraftJar() throws IOException {
- String fromM = "official";
+ private TinyRemapper buildRemapper() throws IOException {
+ Path[] libraries = getRemapClasspath(getProject());
+ TinyRemapper remapper = getTinyRemapper();
+ remapper.readClassPath(libraries);
+ remapper.prepareClasses();
+ return remapper;
+ }
+
+ private byte[][] inputBytes(Path input) throws IOException {
+ List<byte[]> inputByteList = new ArrayList<>();
+
+ try (FileSystemUtil.FileSystemDelegate inputFs = FileSystemUtil.getJarFileSystem(input, false)) {
+ ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter();
+
+ for (Path path : (Iterable<? extends Path>) Files.walk(inputFs.get().getPath("/"))::iterator) {
+ if (Files.isRegularFile(path)) {
+ if (path.getFileName().toString().endsWith(".class")) {
+ taskCompleter.add(() -> {
+ byte[] bytes = Files.readAllBytes(path);
+
+ synchronized (inputByteList) {
+ inputByteList.add(bytes);
+ }
+ });
+ }
+ }
+ }
+
+ taskCompleter.complete();
+ }
- MappingsProviderImpl mappingsProvider = getExtension().getMappingsProvider();
+ return inputByteList.toArray(new byte[0][0]);
+ }
+
+ private void assetsOut(Path input, @Nullable Path assetsOut) throws IOException {
+ if (assetsOut != null) {
+ try (OutputConsumerPath tmpAssetsPath = new OutputConsumerPath.Builder(assetsOut).assumeArchive(true).build()) {
+ if (getExtension().isForge()) {
+ tmpAssetsPath.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, null);
+ } else {
+ tmpAssetsPath.addNonClassFiles(input);
+ }
+ }
+ }
+ }
- Path input = minecraftProvider.getMergedJar().toPath();
+ private void mapMinecraftJar(TinyRemapper[] remapperArray) throws Exception {
+ Path input = inputJar.toPath();
+ Path inputForge = inputForgeJar == null ? null : inputForgeJar.toPath();
Path outputMapped = minecraftMappedJar.toPath();
Path outputIntermediary = minecraftIntermediaryJar.toPath();
+ Path outputSrg = minecraftSrgJar == null ? null : minecraftSrgJar.toPath();
+
+ Path forgeOutputMapped = forgeMappedJar == null ? null : forgeMappedJar.toPath();
+ Path forgeOutputIntermediary = forgeIntermediaryJar == null ? null : forgeIntermediaryJar.toPath();
+ Path forgeOutputSrg = forgeSrgJar == null ? null : forgeSrgJar.toPath();
+
+ Path vanillaAssets = Files.createTempFile("assets", null);
+ Files.deleteIfExists(vanillaAssets);
+ vanillaAssets.toFile().deleteOnExit();
+ Path forgeAssets = Files.createTempFile("assets", null);
+ Files.deleteIfExists(forgeAssets);
+ forgeAssets.toFile().deleteOnExit();
+
+ Info vanilla = new Info(vanillaAssets, input, outputMapped, outputIntermediary, outputSrg);
+ Info forge = getExtension().isForge() ? new Info(forgeAssets, inputForge, forgeOutputMapped, forgeOutputIntermediary, forgeOutputSrg) : null;
- for (String toM : Arrays.asList("named", "intermediary")) {
- Path output = "named".equals(toM) ? outputMapped : outputIntermediary;
+ TinyRemapper remapper = remapperArray[0] = buildRemapper();
+ assetsOut(input, vanillaAssets);
+
+ if (getExtension().isForge()) {
+ assetsOut(inputForge, forgeAssets);
+ }
+
+ remap(remapper, vanilla, forge, "official");
+ }
+
+ public static class Info {
+ Path assets;
+ Path input;
+ Path outputMapped;
+ Path outputIntermediary;
+ Path outputSrg;
+
+ public Info(Path assets, Path input, Path outputMapped, Path outputIntermediary, Path outputSrg) {
+ this.assets = assets;
+ this.input = input;
+ this.outputMapped = outputMapped;
+ this.outputIntermediary = outputIntermediary;
+ this.outputSrg = outputSrg;
+ }
+ }
+
+ public void remap(TinyRemapper remapper, Info vanilla, @Nullable Info forge, String fromM) throws IOException {
+ for (String toM : getExtension().isForge() ? Arrays.asList("intermediary", "srg", "named") : Arrays.asList("intermediary", "named")) {
+ Path output = "named".equals(toM) ? vanilla.outputMapped : "srg".equals(toM) ? vanilla.outputSrg : vanilla.outputIntermediary;
+ Path outputForge = forge == null ? null : "named".equals(toM) ? forge.outputMapped : "srg".equals(toM) ? forge.outputSrg : forge.outputIntermediary;
+ InputTag vanillaTag = remapper.createInputTag();
+ InputTag forgeTag = remapper.createInputTag();
+ Stopwatch stopwatch = Stopwatch.createStarted();
getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")");
- Files.deleteIfExists(output);
+ remapper.readInputs(vanillaTag, vanilla.input);
- TinyRemapper remapper = getTinyRemapper(fromM, toM);
+ if (forge != null) {
+ remapper.readInputs(forgeTag, forge.input);
+ }
+
+ remapper.replaceMappings(getMappings(vanilla.input, fromM, toM));
+ OutputRemappingHandler.remap(remapper, vanilla.assets, output, null, vanillaTag);
+
+ if (forge != null) {
+ OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag);
+ }
- try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) {
- outputConsumer.addNonClassFiles(input);
- remapper.readClassPath(getRemapClasspath());
- remapper.readInputs(input);
- remapper.apply(outputConsumer);
- } catch (Exception e) {
- throw new RuntimeException("Failed to remap JAR " + input + " with mappings from " + mappingsProvider.tinyMappings, e);
- } finally {
- remapper.finish();
+ getProject().getLogger().lifecycle(":remapped minecraft (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch);
+ remapper.removeInput();
+
+ if (getExtension().isForge() && !"srg".equals(toM)) {
+ getProject().getLogger().info(":running minecraft finalising tasks");
+
+ TinyTree yarnWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg();
+ AtRemapper.remap(getProject().getLogger(), output, yarnWithSrg);
+ CoreModClassRemapper.remapJar(output, yarnWithSrg, getProject().getLogger());
}
}
}
- public TinyRemapper getTinyRemapper(String fromM, String toM) throws IOException {
- return TinyRemapper.newRemapper()
- .withMappings(TinyRemapperMappingsHelper.create(getExtension().getMappingsProvider().getMappings(), fromM, toM, true))
- .withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass))
+ public TinyRemapper getTinyRemapper() throws IOException {
+ TinyRemapper.Builder builder = TinyRemapper.newRemapper()
.renameInvalidLocals(true)
- .rebuildSourceFilenames(true)
- .build();
+ .logUnknownInvokeDynamic(false)
+ .ignoreConflicts(getExtension().isForge())
+ .cacheMappings(true)
+ .threads(Runtime.getRuntime().availableProcessors())
+ .logger(getProject().getLogger()::lifecycle)
+ .rebuildSourceFilenames(true);
+
+ if (getExtension().isForge()) {
+ /* FORGE: Required for classes like aej$OptionalNamedTag (1.16.4) which are added by Forge patches.
+ * They won't get remapped to their proper packages, so IllegalAccessErrors will happen without ._.
+ */
+ builder.fixPackageAccess(true);
+ }
+
+ return builder.build();
}
- public Path[] getRemapClasspath() {
- return getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles()
+ public Set<IMappingProvider> getMappings(@Nullable Path fromJar, String fromM, String toM) throws IOException {
+ Set<IMappingProvider> providers = new HashSet<>();
+ providers.add(TinyRemapperMappingsHelper.create(getExtension().isForge() ? getExtension().getMappingsProvider().getMappingsWithSrg() : getExtension().getMappingsProvider().getMappings(), fromM, toM, true));
+
+ if (getExtension().isForge()) {
+ if (fromJar != null) {
+ providers.add(InnerClassRemapper.of(fromJar, getExtension().getMappingsProvider().getMappingsWithSrg(), fromM, toM));
+ }
+ } else {
+ providers.add(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass));
+ }
+
+ return providers;
+ }
+
+ public static Path[] getRemapClasspath(Project project) {
+ return project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles()
.stream().map(File::toPath).toArray(Path[]::new);
}
@@ -151,7 +353,16 @@ public class MinecraftMappedProvider extends DependencyProvider {
public void initFiles(MinecraftProviderImpl minecraftProvider, MappingsProviderImpl mappingsProvider) {
this.minecraftProvider = minecraftProvider;
minecraftIntermediaryJar = new File(getDirectories().getUserCache(), "minecraft-" + getJarVersionString("intermediary") + ".jar");
+ minecraftSrgJar = !getExtension().isForge() ? null : new File(getDirectories().getUserCache(), "minecraft-" + getJarVersionString("srg") + ".jar");
minecraftMappedJar = new File(getJarDirectory(getDirectories().getUserCache(), "mapped"), "minecraft-" + getJarVersionString("mapped") + ".jar");
+ inputJar = getExtension().isForge() ? mappingsProvider.patchedProvider.getMergedJar() : minecraftProvider.getMergedJar();
+
+ if (getExtension().isForge()) {
+ inputForgeJar = mappingsProvider.patchedProvider.getForgeMergedJar();
+ forgeIntermediaryJar = new File(getDirectories().getUserCache(), "forge-" + getJarVersionString("intermediary") + ".jar");
+ forgeSrgJar = new File(getDirectories().getUserCache(), "forge-" + getJarVersionString("srg") + ".jar");
+ forgeMappedJar = new File(getJarDirectory(getDirectories().getUserCache(), "mapped"), "forge/forge-" + getJarVersionString("mapped") + ".jar");
+ }
}
protected File getJarDirectory(File parentDirectory, String type) {
@@ -159,17 +370,33 @@ public class MinecraftMappedProvider extends DependencyProvider {
}
protected String getJarVersionString(String type) {
- return String.format("%s-%s-%s-%s", minecraftProvider.minecraftVersion(), type, getExtension().getMappingsProvider().mappingsName, getExtension().getMappingsProvider().mappingsVersion);
+ return String.format("%s-%s-%s-%s%s", minecraftProvider.minecraftVersion(), type, getExtension().getMappingsProvider().mappingsName, getExtension().getMappingsProvider().mappingsVersion, minecraftProvider.getJarSuffix());
}
public File getIntermediaryJar() {
return minecraftIntermediaryJar;
}
+ public File getSrgJar() {
+ return minecraftSrgJar;
+ }
+
public File getMappedJar() {
return minecraftMappedJar;
}
+ public File getForgeIntermediaryJar() {
+ return forgeIntermediaryJar;
+ }
+
+ public File getForgeSrgJar() {
+ return forgeSrgJar;
+ }
+
+ public File getForgeMappedJar() {
+ return forgeMappedJar;
+ }
+
public File getUnpickedJar() {
return new File(getJarDirectory(getDirectories().getUserCache(), "mapped"), "minecraft-" + getJarVersionString("unpicked") + ".jar");
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java
index 1b37604f..13fc8f0f 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java
@@ -28,14 +28,16 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
-import java.util.Deque;
import java.util.Map;
-import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Stopwatch;
+import me.tongfei.progressbar.DelegatingProgressBarConsumer;
+import me.tongfei.progressbar.ProgressBar;
+import me.tongfei.progressbar.ProgressBarBuilder;
+import me.tongfei.progressbar.ProgressBarStyle;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
@@ -45,7 +47,6 @@ import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.HashedDownloadUtil;
-import net.fabricmc.loom.util.gradle.ProgressLogger;
public class MinecraftAssetsProvider {
public static void provide(MinecraftProviderImpl minecraftProvider, Project project) throws IOException {
@@ -78,8 +79,8 @@ public class MinecraftAssetsProvider {
HashedDownloadUtil.downloadIfInvalid(new URL(assetIndex.url()), assetsInfo, assetIndex.sha1(), project.getLogger(), false);
}
- Deque<ProgressLogger> loggers = new ConcurrentLinkedDeque<>();
- ExecutorService executor = Executors.newFixedThreadPool(Math.min(10, Math.max(Runtime.getRuntime().availableProcessors() / 2, 1)));
+ ExecutorService executor = Executors.newFixedThreadPool(Math.min(16, Math.max(Runtime.getRuntime().availableProcessors() * 2, 1)));
+ int toDownload = 0;
AssetIndex index;
@@ -91,72 +92,78 @@ public class MinecraftAssetsProvider {
Map<String, AssetObject> parent = index.objects();
- for (Map.Entry<String, AssetObject> entry : parent.entrySet()) {
- AssetObject object = entry.getValue();
- String sha1 = object.hash();
- String filename = "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1;
- File file = new File(assets, filename);
-
- if (offline) {
- if (file.exists()) {
- project.getLogger().warn("Outdated asset " + entry.getKey());
- } else {
- throw new GradleException("Asset " + entry.getKey() + " not found at " + file.getAbsolutePath());
- }
- } else {
- executor.execute(() -> {
- final String[] assetName = {entry.getKey()};
- int end = assetName[0].lastIndexOf("/") + 1;
+ ProgressBar[] progressBar = {null};
- if (end > 0) {
- assetName[0] = assetName[0].substring(end);
+ try {
+ for (Map.Entry<String, AssetObject> entry : parent.entrySet()) {
+ AssetObject object = entry.getValue();
+ String sha1 = object.hash();
+ String filename = "objects" + File.separator + sha1.substring(0, 2) + File.separator + sha1;
+ File file = new File(assets, filename);
+
+ if (offline) {
+ if (file.exists()) {
+ project.getLogger().warn("Outdated asset " + entry.getKey());
+ } else {
+ throw new GradleException("Asset " + entry.getKey() + " not found at " + file.getAbsolutePath());
+ }
+ } else if (HashedDownloadUtil.requiresDownload(file, sha1, project.getLogger())) {
+ toDownload++;
+
+ synchronized (progressBar) {
+ if (progressBar[0] == null) {
+ progressBar[0] = new ProgressBarBuilder()
+ .setConsumer(new DelegatingProgressBarConsumer(project.getLogger()::lifecycle))
+ .setInitialMax(toDownload)
+ .setUpdateIntervalMillis(2000)
+ .setTaskName(":downloading assets")
+ .setStyle(ProgressBarStyle.ASCII)
+ .showSpeed()
+ .build();
+ }
+
+ progressBar[0].maxHint(toDownload);
}
- project.getLogger().debug("validating asset " + assetName[0]);
-
- final ProgressLogger[] progressLogger = new ProgressLogger[1];
+ executor.execute(() -> {
+ String assetName = entry.getKey();
+ int end = assetName.lastIndexOf("/") + 1;
- try {
- HashedDownloadUtil.downloadIfInvalid(new URL(Constants.RESOURCES_BASE + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> {
- ProgressLogger logger = loggers.pollFirst();
+ if (end > 0) {
+ assetName = assetName.substring(end);
+ }
- if (logger == null) {
- //Create a new logger if we need one
- progressLogger[0] = ProgressLogger.getProgressFactory(project, MinecraftAssetsProvider.class.getName());
- progressLogger[0].start("Downloading assets...", "assets");
- } else {
- // use a free logger if we can
- progressLogger[0] = logger;
- }
+ project.getLogger().debug(":downloading asset " + assetName);
- project.getLogger().debug("downloading asset " + assetName[0]);
- progressLogger[0].progress(String.format("%-30.30s", assetName[0]) + " - " + sha1);
- });
- } catch (IOException e) {
- throw new RuntimeException("Failed to download: " + assetName[0], e);
- }
+ try {
+ HashedDownloadUtil.downloadIfInvalid(new URL(Constants.RESOURCES_BASE + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, false);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to download: " + assetName, e);
+ }
- if (progressLogger[0] != null) {
- //Give this logger back if we used it
- loggers.add(progressLogger[0]);
- }
- });
+ synchronized (progressBar) {
+ progressBar[0].step();
+ }
+ });
+ }
}
- }
- project.getLogger().info("Took " + stopwatch.stop() + " to iterate " + parent.size() + " asset index.");
+ project.getLogger().info("Took " + stopwatch.stop() + " to iterate " + parent.size() + " asset index.");
- //Wait for the assets to all download
- executor.shutdown();
+ //Wait for the assets to all download
+ executor.shutdown();
- try {
- if (executor.awaitTermination(2, TimeUnit.HOURS)) {
- executor.shutdownNow();
+ try {
+ if (executor.awaitTermination(2, TimeUnit.HOURS)) {
+ executor.shutdownNow();
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ } finally {
+ if (progressBar[0] != null) {
+ progressBar[0].close();
}
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
}
-
- loggers.forEach(ProgressLogger::completed);
}
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/MercuryUtils.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/MercuryUtils.java
new file mode 100644
index 00000000..5ac28e61
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/MercuryUtils.java
@@ -0,0 +1,42 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2021 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.tr;
+
+import org.cadixdev.mercury.Mercury;
+
+public class MercuryUtils {
+ public static Mercury copyMercury(Mercury mercury) {
+ Mercury copy = new Mercury();
+ copy.getClassPath().addAll(mercury.getClassPath());
+ copy.getContext().putAll(mercury.getContext());
+ copy.getProcessors().addAll(mercury.getProcessors());
+ copy.setEncoding(mercury.getEncoding());
+ copy.setFlexibleAnonymousClassMemberLookups(mercury.isFlexibleAnonymousClassMemberLookups());
+ copy.setGracefulClasspathChecks(mercury.isGracefulClasspathChecks());
+ copy.setGracefulJavadocClasspathChecks(mercury.isGracefulJavadocClasspathChecks());
+ copy.setSourceCompatibility(mercury.getSourceCompatibility());
+ return copy;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java
new file mode 100644
index 00000000..96f99265
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java
@@ -0,0 +1,82 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2021 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.tr;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.function.BiConsumer;
+
+import dev.architectury.tinyremapper.InputTag;
+import dev.architectury.tinyremapper.TinyRemapper;
+
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.FileSystemUtil.FileSystemDelegate;
+import net.fabricmc.loom.util.ThreadingUtils;
+
+public class OutputRemappingHandler {
+ public static void remap(TinyRemapper remapper, Path assets, Path output) throws IOException {
+ remap(remapper, assets, output, null);
+ }
+
+ public static void remap(TinyRemapper remapper, Path assets, Path output, BiConsumer<String, byte[]> then) throws IOException {
+ remap(remapper, assets, output, then, (InputTag[]) null);
+ }
+
+ public static void remap(TinyRemapper remapper, Path assets, Path output, BiConsumer<String, byte[]> then, InputTag... inputTags) throws IOException {
+ Files.copy(assets, output, StandardCopyOption.REPLACE_EXISTING);
+
+ try (FileSystemDelegate system = FileSystemUtil.getJarFileSystem(output, true)) {
+ ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter();
+
+ remapper.apply((path, bytes) -> {
+ if (path.startsWith("/")) path = path.substring(1);
+
+ try {
+ Path fsPath = system.get().getPath(path + ".class");
+
+ if (fsPath.getParent() != null) {
+ Files.createDirectories(fsPath.getParent());
+ }
+
+ taskCompleter.add(() -> {
+ Files.write(fsPath, bytes, StandardOpenOption.CREATE);
+ });
+
+ if (then != null) {
+ then.accept(path, bytes);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }, inputTags);
+
+ taskCompleter.complete();
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java
new file mode 100644
index 00000000..db56ae5e
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java
@@ -0,0 +1,237 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2021 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.sources;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+
+import org.cadixdev.lorenz.MappingSet;
+import org.cadixdev.mercury.Mercury;
+import org.cadixdev.mercury.remapper.MercuryRemapper;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.zeroturnaround.zip.ZipUtil;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.build.ModCompileRemapper;
+import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;
+import net.fabricmc.loom.task.GenerateSourcesTask;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.DeletingFileVisitor;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.SourceRemapper;
+import net.fabricmc.loom.util.ThreadingUtils;
+import net.fabricmc.lorenztiny.TinyMappingsReader;
+
+public class ForgeSourcesRemapper {
+ public static void addBaseForgeSources(Project project) throws IOException {
+ Path sourcesJar = GenerateSourcesTask.getMappedJarFileWithSuffix(project, "-sources.jar", true).toPath();
+
+ if (!Files.exists(sourcesJar)) {
+ addForgeSources(project, sourcesJar);
+ }
+ }
+
+ public static void addForgeSources(Project project, Path sourcesJar) throws IOException {
+ try (FileSystemUtil.FileSystemDelegate delegate = FileSystemUtil.getJarFileSystem(sourcesJar, true)) {
+ ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter();
+
+ provideForgeSources(project, (path, bytes) -> {
+ Path fsPath = delegate.get().getPath(path);
+
+ if (fsPath.getParent() != null) {
+ try {
+ Files.createDirectories(fsPath.getParent());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ taskCompleter.add(() -> {
+ Files.write(fsPath, bytes, StandardOpenOption.CREATE);
+ });
+ });
+
+ taskCompleter.complete();
+ }
+ }
+
+ public static void provideForgeSources(Project project, BiConsumer<String, byte[]> consumer) throws IOException {
+ List<Path> forgeInstallerSources = new ArrayList<>();
+
+ for (ResolvedArtifact artifact : project.getConfigurations().getByName(Constants.Configurations.FORGE_INSTALLER).getResolvedConfiguration().getResolvedArtifacts()) {
+ File forgeInstallerSource = ModCompileRemapper.findSources(project.getDependencies(), artifact);
+
+ if (forgeInstallerSource != null) {
+ forgeInstallerSources.add(forgeInstallerSource.toPath());
+ }
+ }
+
+ project.getLogger().lifecycle(":found {} forge source jars", forgeInstallerSources.size());
+ Map<String, byte[]> forgeSources = extractSources(forgeInstallerSources);
+ project.getLogger().lifecycle(":extracted {} forge source classes", forgeSources.size());
+ remapSources(project, forgeSources);
+ forgeSources.forEach(consumer);
+ }
+
+ private static void remapSources(Project project, Map<String, byte[]> sources) throws IOException {
+ File tmpInput = File.createTempFile("tmpInputForgeSources", null);
+ tmpInput.delete();
+ tmpInput.deleteOnExit();
+ File tmpOutput = File.createTempFile("tmpOutputForgeSources", null);
+ tmpOutput.delete();
+ tmpOutput.deleteOnExit();
+
+ try (FileSystemUtil.FileSystemDelegate delegate = FileSystemUtil.getJarFileSystem(tmpInput, true)) {
+ ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter();
+
+ for (Map.Entry<String, byte[]> entry : sources.entrySet()) {
+ Path path = delegate.get().getPath(entry.getKey());
+
+ if (path.getParent() != null) {
+ Files.createDirectories(path.getParent());
+ }
+
+ taskCompleter.add(() -> {
+ Files.write(path, entry.getValue(), StandardOpenOption.CREATE);
+ });
+ }
+
+ taskCompleter.complete();
+ }
+
+ remapForgeSourcesInner(project, tmpInput.toPath(), tmpOutput.toPath());
+ tmpInput.delete();
+ int[] failedToRemap = {0};
+
+ try (FileSystemUtil.FileSystemDelegate delegate = FileSystemUtil.getJarFileSystem(tmpOutput, false)) {
+ ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter();
+
+ for (Map.Entry<String, byte[]> entry : new HashSet<>(sources.entrySet())) {
+ taskCompleter.add(() -> {
+ Path path = delegate.get().getPath(entry.getKey());
+
+ if (Files.exists(path)) {
+ sources.put(entry.getKey(), Files.readAllBytes(path));
+ } else {
+ sources.remove(entry.getKey());
+ project.getLogger().error("forge source failed to remap " + entry.getKey());
+ failedToRemap[0]++;
+ }
+ });
+ }
+
+ taskCompleter.complete();
+ }
+
+ tmpOutput.delete();
+
+ if (failedToRemap[0] > 0) {
+ project.getLogger().error("{} forge sources failed to remap", failedToRemap[0]);
+ }
+ }
+
+ private static void remapForgeSourcesInner(Project project, Path tmpInput, Path tmpOutput) throws IOException {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+ Mercury mercury = SourceRemapper.createMercuryWithClassPath(project, false);
+
+ MappingSet mappings = new TinyMappingsReader(extension.getMappingsProvider().getMappingsWithSrg(), "srg", "named").read();
+
+ for (Map.Entry<String, String> entry : MinecraftMappedProvider.JSR_TO_JETBRAINS.entrySet()) {
+ mappings.getOrCreateClassMapping(entry.getKey()).setDeobfuscatedName(entry.getValue());
+ }
+
+ Set<File> files = project.getConfigurations()
+ .detachedConfiguration(project.getDependencies().create(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS))
+ .resolve();
+
+ for (File file : files) {
+ mercury.getClassPath().add(file.toPath());
+ }
+
+ // Distinct and add the srg jar at the top, so it gets prioritized
+ mercury.getClassPath().add(0, extension.getMinecraftMappedProvider().getSrgJar().toPath());
+ mercury.getClassPath().add(0, extension.getMinecraftMappedProvider().getForgeSrgJar().toPath());
+ List<Path> newClassPath = mercury.getClassPath().stream()
+ .distinct()
+ .filter(Files::isRegularFile)
+ .collect(Collectors.toList());
+ mercury.getClassPath().clear();
+ mercury.getClassPath().addAll(newClassPath);
+
+ mercury.getProcessors().add(MercuryRemapper.create(mappings));
+ boolean isSrcTmp = false;
+
+ if (!Files.isDirectory(tmpInput)) {
+ Path tmpInput1 = tmpInput;
+ // create tmp directory
+ isSrcTmp = true;
+ tmpInput = Files.createTempDirectory("fabric-loom-src");
+ ZipUtil.unpack(tmpInput1.toFile(), tmpInput.toFile());
+ }
+
+ try (FileSystemUtil.FileSystemDelegate outputFs = FileSystemUtil.getJarFileSystem(tmpOutput, true)) {
+ Path outputFsRoot = outputFs.get().getPath("/");
+ mercury.rewrite(tmpInput, outputFsRoot);
+ } catch (Exception e) {
+ project.getLogger().warn("Could not remap " + tmpInput + " fully!", e);
+ }
+
+ if (isSrcTmp) {
+ Files.walkFileTree(tmpInput, new DeletingFileVisitor());
+ }
+ }
+
+ private static Map<String, byte[]> extractSources(List<Path> forgeInstallerSources) throws IOException {
+ Map<String, byte[]> sources = new ConcurrentHashMap<>();
+ ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter();
+
+ for (Path path : forgeInstallerSources) {
+ FileSystemUtil.FileSystemDelegate system = FileSystemUtil.getJarFileSystem(path, false);
+ taskCompleter.onComplete(stopwatch -> system.close());
+
+ for (Path filePath : (Iterable<? extends Path>) Files.walk(system.get().getPath("/"))::iterator) {
+ if (Files.isRegularFile(filePath) && filePath.getFileName().toString().endsWith(".java")) {
+ taskCompleter.add(() -> sources.put(filePath.toString(), Files.readAllBytes(filePath)));
+ }
+ }
+ }
+
+ taskCompleter.complete();
+ return sources;
+ }
+}