aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/net/fabricmc/loom
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/net/fabricmc/loom')
-rw-r--r--src/main/java/net/fabricmc/loom/LoomGradleExtension.java50
-rw-r--r--src/main/java/net/fabricmc/loom/LoomGradlePlugin.java11
-rw-r--r--src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java19
-rw-r--r--src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java151
-rw-r--r--src/main/java/net/fabricmc/loom/api/ForgeLocalMod.java88
-rw-r--r--src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java52
-rw-r--r--src/main/java/net/fabricmc/loom/api/decompilers/architectury/ArchitecturyLoomDecompiler.java55
-rw-r--r--src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java1
-rw-r--r--src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java2
-rw-r--r--src/main/java/net/fabricmc/loom/build/JarRemapper.java36
-rw-r--r--src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java2
-rw-r--r--src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java33
-rw-r--r--src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java8
-rw-r--r--src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java38
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java75
-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.java15
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/MavenPublication.java7
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java37
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java38
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java2
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java42
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java30
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java8
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java94
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java95
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java8
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java13
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java74
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java53
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java282
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java115
-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.java317
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java232
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java786
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java88
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java273
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java18
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java55
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java178
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java49
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java36
-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.java15
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java42
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java4
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java302
-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/OutputRemappingHandler.java81
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java259
-rw-r--r--src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java136
-rw-r--r--src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java121
-rw-r--r--src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java31
-rw-r--r--src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java81
-rw-r--r--src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java1
-rw-r--r--src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java5
-rw-r--r--src/main/java/net/fabricmc/loom/task/AbstractRunTask.java1
-rw-r--r--src/main/java/net/fabricmc/loom/task/ArchitecturyGenerateSourcesTask.java96
-rw-r--r--src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java1
-rw-r--r--src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java136
-rw-r--r--src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java34
-rw-r--r--src/main/java/net/fabricmc/loom/task/LoomTasks.java43
-rw-r--r--src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java16
-rw-r--r--src/main/java/net/fabricmc/loom/task/RemapJarTask.java212
-rw-r--r--src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java18
-rw-r--r--src/main/java/net/fabricmc/loom/task/RunDataTask.java38
-rw-r--r--src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java4
-rw-r--r--src/main/java/net/fabricmc/loom/util/Checksum.java11
-rw-r--r--src/main/java/net/fabricmc/loom/util/Constants.java42
-rw-r--r--src/main/java/net/fabricmc/loom/util/DependencyDownloader.java97
-rw-r--r--src/main/java/net/fabricmc/loom/util/DownloadUtil.java12
-rw-r--r--src/main/java/net/fabricmc/loom/util/FileSystemUtil.java2
-rw-r--r--src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java42
-rw-r--r--src/main/java/net/fabricmc/loom/util/LfWriter.java43
-rw-r--r--src/main/java/net/fabricmc/loom/util/LoggerFilter.java49
-rw-r--r--src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java115
-rw-r--r--src/main/java/net/fabricmc/loom/util/ModPlatform.java50
-rw-r--r--src/main/java/net/fabricmc/loom/util/PropertyUtil.java45
-rw-r--r--src/main/java/net/fabricmc/loom/util/SourceRemapper.java53
-rw-r--r--src/main/java/net/fabricmc/loom/util/ThreadingUtils.java192
-rw-r--r--src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java77
-rw-r--r--src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java118
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java75
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java39
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/IoConsumer.java38
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/LazyBool.java52
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java140
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java115
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java101
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/MCPReader.java363
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/MappingException.java36
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java148
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java406
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java47
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java144
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java114
99 files changed, 7902 insertions, 304 deletions
diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
index 060a094b..85855067 100644
--- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
+++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
@@ -42,6 +42,13 @@ import net.fabricmc.loom.configuration.LoomDependencyManager;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
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.configuration.providers.minecraft.MinecraftMappedProvider;
import net.fabricmc.loom.extension.LoomFiles;
@@ -81,7 +88,7 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
}
default MappingsProviderImpl getMappingsProvider() {
- return getDependencyManager().getProvider(MappingsProviderImpl.class);
+ return getDependencyManager().getProvider(isForge() ? FieldMigratedMappingsProvider.class : MappingsProviderImpl.class);
}
default MinecraftMappedProvider getMinecraftMappedProvider() {
@@ -108,4 +115,45 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
List<AccessWidenerFile> getTransitiveAccessWideners();
void addTransitiveAccessWideners(List<AccessWidenerFile> accessWidenerFiles);
+
+ // ===================
+ // Architectury Loom
+ // ===================
+ default PatchProvider getPatchProvider() {
+ return getDependencyManager().getProvider(PatchProvider.class);
+ }
+
+ default McpConfigProvider getMcpConfigProvider() {
+ return getDependencyManager().getProvider(McpConfigProvider.class);
+ }
+
+ default boolean isDataGenEnabled() {
+ return isForge() && !getForge().getDataGenMods().isEmpty();
+ }
+
+ default boolean isForgeAndOfficial() {
+ return isForge() && getMcpConfigProvider().isOfficial();
+ }
+
+ default boolean isForgeAndNotOfficial() {
+ return isForge() && !getMcpConfigProvider().isOfficial();
+ }
+
+ boolean supportsInclude();
+
+ default SrgProvider getSrgProvider() {
+ return getDependencyManager().getProvider(SrgProvider.class);
+ }
+
+ default ForgeUniversalProvider getForgeUniversalProvider() {
+ return getDependencyManager().getProvider(ForgeUniversalProvider.class);
+ }
+
+ default ForgeUserdevProvider getForgeUserdevProvider() {
+ return getDependencyManager().getProvider(ForgeUserdevProvider.class);
+ }
+
+ default ForgeProvider getForgeProvider() {
+ return getDependencyManager().getProvider(ForgeProvider.class);
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java
index 63e6e871..bdc94f3a 100644
--- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java
+++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java
@@ -25,6 +25,9 @@
package net.fabricmc.loom;
import java.util.Objects;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -62,7 +65,13 @@ public class LoomGradlePlugin implements BootstrappedPlugin {
}
public void apply(Project project) {
- project.getLogger().lifecycle("Fabric Loom: " + LOOM_VERSION);
+ Set<String> loggedVersions = new HashSet<>(Arrays.asList(System.getProperty("loom.printed.logged", "").split(",")));
+
+ if (!loggedVersions.contains(LOOM_VERSION)) {
+ loggedVersions.add(LOOM_VERSION);
+ System.setProperty("loom.printed.logged", String.join(",", loggedVersions));
+ project.getLogger().lifecycle("Architectury Loom: " + LOOM_VERSION);
+ }
refreshDeps = project.getGradle().getStartParameter().isRefreshDependencies() || Boolean.getBoolean("loom.refresh");
diff --git a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java
index 48b61e6d..28045071 100644
--- a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java
+++ b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java
@@ -64,6 +64,13 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> {
repo.setUrl(files.getRemappedModCache());
});
repositories.maven(repo -> {
+ repo.setName("Architectury");
+ repo.setUrl("https://maven.architectury.dev/");
+ repo.mavenContent(content -> {
+ content.includeGroup("dev.architectury");
+ });
+ });
+ repositories.maven(repo -> {
repo.setName("Fabric");
repo.setUrl(MirrorUtil.getFabricRepository(target));
});
@@ -71,6 +78,18 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> {
repo.setName("Mojang");
repo.setUrl(MirrorUtil.getLibrariesBase(target));
});
+ repositories.maven(repo -> {
+ repo.setName("Forge");
+ repo.setUrl("https://maven.minecraftforge.net/");
+
+ repo.content(descriptor -> {
+ descriptor.excludeGroupByRegex("org\\.eclipse\\.?.*");
+ });
+ repo.metadataSources(sources -> {
+ sources.mavenPom();
+ sources.ignoreGradleMetadataRedirection();
+ });
+ });
repositories.mavenCentral();
repositories.ivy(repo -> {
diff --git a/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java
new file mode 100644
index 00000000..e455d742
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java
@@ -0,0 +1,151 @@
+/*
+ * 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.api;
+
+import java.util.List;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.provider.Property;
+import org.gradle.api.provider.SetProperty;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * This is the forge extension api available exposed to build scripts.
+ */
+@ApiStatus.NonExtendable
+public interface ForgeExtensionAPI {
+ /**
+ * If true, {@linkplain LoomGradleExtensionAPI#getAccessWidenerPath() the project access widener file}
+ * will be remapped to an access transformer file if set.
+ *
+ * @return the property
+ */
+ Property<Boolean> getConvertAccessWideners();
+
+ /**
+ * A set of additional access widener files that will be converted to access transformers
+ * {@linkplain #getConvertAccessWideners() if enabled}. The files are specified as paths in jar files
+ * (e.g. {@code path/to/my_aw.accesswidener}).
+ *
+ * @return the property
+ */
+ SetProperty<String> getExtraAccessWideners();
+
+ /**
+ * A collection of all project access transformers.
+ * The collection should only contain AT files, and not directories or other files.
+ *
+ * <p>If this collection is empty, Loom tries to resolve the AT from the default path
+ * ({@code META-INF/accesstransformer.cfg} in the {@code main} source set).
+ *
+ * @return the collection of AT files
+ */
+ ConfigurableFileCollection getAccessTransformers();
+
+ /**
+ * Adds a {@linkplain #getAccessTransformers() project access transformer}.
+ *
+ * @param file the file, evaluated as per {@link org.gradle.api.Project#file(Object)}
+ */
+ void accessTransformer(Object file);
+
+ /**
+ * A set of all mixin configs related to source set resource roots.
+ * All mixin configs must be added to this property so that they apply in a dev environment.
+ *
+ * @return the property
+ */
+ SetProperty<String> getMixinConfigs();
+
+ /**
+ * Adds mixin config files to {@link #getMixinConfigs() mixinConfigs}.
+ *
+ * @param mixinConfigs the mixin config file paths relative to resource roots
+ */
+ void mixinConfigs(String... mixinConfigs);
+
+ /**
+ * Adds mixin config files to {@link #getMixinConfigs() mixinConfigs}.
+ *
+ * @param mixinConfigs the mixin config file paths relative to resource roots
+ */
+ default void mixinConfig(String... mixinConfigs) {
+ mixinConfigs(mixinConfigs);
+ }
+
+ /**
+ * If true, upstream Mixin from Sponge will be replaced with Fabric's or Architectury's fork.
+ * This is enabled by default.
+ *
+ * @return the property
+ */
+ Property<Boolean> getUseCustomMixin();
+
+ /**
+ * A list of mod IDs for mods applied for data generation.
+ * The returned list is unmodifiable but not immutable - it will reflect changes done with
+ * {@link #dataGen(Action)}.
+ *
+ * @return the list
+ */
+ List<String> getDataGenMods();
+
+ /**
+ * Applies data generation settings.
+ *
+ * @param action the action to configure data generation
+ */
+ void dataGen(Action<DataGenConsumer> action);
+
+ /**
+ * Data generation config.
+ */
+ @ApiStatus.NonExtendable
+ interface DataGenConsumer {
+ /**
+ * Adds mod IDs applied for data generation.
+ *
+ * @param modIds the mod IDs
+ */
+ void mod(String... modIds);
+ }
+
+ /**
+ * Configures local mods.
+ *
+ * @param action the configuration action
+ */
+ void localMods(Action<NamedDomainObjectContainer<ForgeLocalMod>> action);
+
+ /**
+ * The container of local mods applied to run configs.
+ *
+ * @return the container
+ * @see ForgeLocalMod
+ */
+ NamedDomainObjectContainer<ForgeLocalMod> getLocalMods();
+}
diff --git a/src/main/java/net/fabricmc/loom/api/ForgeLocalMod.java b/src/main/java/net/fabricmc/loom/api/ForgeLocalMod.java
new file mode 100644
index 00000000..b722eb9e
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/api/ForgeLocalMod.java
@@ -0,0 +1,88 @@
+/*
+ * 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.api;
+
+import java.util.List;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.gradle.api.Named;
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.SourceSet;
+
+/**
+ * Data for a mod built from project files in a dev environment.
+ * This data is only used for run config generation (FML needs the paths to mod files).
+ */
+public class ForgeLocalMod implements Named {
+ private final Project project;
+ private final String name;
+ private final List<Supplier<SourceSet>> sourceSets;
+
+ /**
+ * Constructs a local mod.
+ *
+ * @param project the project using this mod
+ * @param name the unique name of this local mod (does not have to correspond to a mod ID)
+ * @param sourceSets the list of source set suppliers corresponding to this mod; must be mutable
+ */
+ public ForgeLocalMod(Project project, String name, List<Supplier<SourceSet>> sourceSets) {
+ this.project = project;
+ this.name = name;
+ this.sourceSets = sourceSets;
+ }
+
+ /**
+ * Adds source sets to this local mod.
+ *
+ * <p>The source sets are resolved like this:
+ * <ul>
+ * <li>a {@link SourceSet} is used as is</li>
+ * <li>all other objects will be converted to source set names with {@link String#valueOf(Object)} and
+ * fetched with {@code sourceSets.findByName(name)}</li>
+ * </ul>
+ *
+ * @param sourceSets the source sets
+ */
+ public void add(Object... sourceSets) {
+ for (Object sourceSet : sourceSets) {
+ if (sourceSet instanceof SourceSet) {
+ this.sourceSets.add(() -> (SourceSet) sourceSet);
+ } else {
+ this.sourceSets.add(() -> project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().findByName(String.valueOf(sourceSet)));
+ }
+ }
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public Stream<SourceSet> getSourceSets() {
+ return sourceSets.stream().map(Supplier::get);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
index 2fd46276..1e939f3c 100644
--- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
+++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java
@@ -26,6 +26,7 @@ package net.fabricmc.loom.api;
import java.io.File;
import java.util.List;
+import java.util.function.Consumer;
import org.gradle.api.Action;
import org.gradle.api.NamedDomainObjectContainer;
@@ -34,14 +35,19 @@ import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
+import org.gradle.api.provider.Provider;
import org.gradle.api.publish.maven.MavenPublication;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
+import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
+import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
+import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.DeprecationHelper;
+import net.fabricmc.loom.util.ModPlatform;
/**
* This is the public api available exposed to build scripts.
@@ -218,4 +224,50 @@ public interface LoomGradleExtensionAPI {
* @return the intermediary url template
*/
Property<String> getIntermediaryUrl();
+
+ // ===================
+ // Architectury Loom
+ // ===================
+ ListProperty<ArchitecturyLoomDecompiler> getArchGameDecompilers();
+
+ default void addArchDecompiler(ArchitecturyLoomDecompiler decompiler) {
+ getArchGameDecompilers().add(decompiler);
+ }
+
+ void silentMojangMappingsLicense();
+
+ boolean isSilentMojangMappingsLicenseEnabled();
+
+ Provider<ModPlatform> getPlatform();
+
+ default boolean isForge() {
+ return getPlatform().get() == ModPlatform.FORGE;
+ }
+
+ void setGenerateSrgTiny(Boolean generateSrgTiny);
+
+ boolean shouldGenerateSrgTiny();
+
+ void launches(Action<NamedDomainObjectContainer<LaunchProviderSettings>> action);
+
+ NamedDomainObjectContainer<LaunchProviderSettings> getLaunchConfigs();
+
+ default void addTaskBeforeRun(String task) {
+ this.getTasksBeforeRun().add(task);
+ }
+
+ List<String> getTasksBeforeRun();
+
+ List<Consumer<RunConfig>> getSettingsPostEdit();
+
+ /**
+ * Gets the Forge extension used to configure Forge details.
+ *
+ * @return the Forge extension
+ * @throws UnsupportedOperationException if running on another platform
+ * @see #isForge()
+ */
+ ForgeExtensionAPI getForge();
+
+ void forge(Action<ForgeExtensionAPI> action);
}
diff --git a/src/main/java/net/fabricmc/loom/api/decompilers/architectury/ArchitecturyLoomDecompiler.java b/src/main/java/net/fabricmc/loom/api/decompilers/architectury/ArchitecturyLoomDecompiler.java
new file mode 100644
index 00000000..c6988b8f
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/api/decompilers/architectury/ArchitecturyLoomDecompiler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.api.decompilers.architectury;
+
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.api.decompilers.LoomDecompiler;
+
+/**
+ * A decompiler definition. Differs from {@link LoomDecompiler} by allowing
+ * to use a project context for creating the decompiler, which lets you make
+ * configurable decompilers.
+ *
+ * <p>Note that JVM forking is not handled by this interface, and that is
+ * the responsibility of the decompiler implementation.
+ */
+public interface ArchitecturyLoomDecompiler {
+ /**
+ * {@return the name of the decompiler}
+ * It is used for naming the source generation task ({@code genSourcesWith[name]}).
+ */
+ String name();
+
+ /**
+ * Creates a {@link LoomDecompiler} from a {@link Project} context.
+ *
+ * <p>The name of the created decompiler is not used.
+ *
+ * @param project the project context
+ * @return the created decompiler
+ */
+ LoomDecompiler create(Project project);
+}
diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java
index ae32f7f9..eb6ae9bc 100644
--- a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java
+++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingsNamespace.java
@@ -43,6 +43,7 @@ public enum MappingsNamespace {
* @see <a href="https://github.com/FabricMC/intermediary/">github.com/FabricMC/intermediary/</a>
*/
INTERMEDIARY,
+ SRG,
/**
* Named mappings are the developer friendly names used to develop mods against.
diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java
index 1ed527e0..1d3de709 100644
--- a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java
+++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java
@@ -71,6 +71,8 @@ public interface LayeredMappingSpecBuilder {
LayeredMappingSpecBuilder parchment(Object object, Action<ParchmentMappingsSpecBuilder> action);
+ LayeredMappingSpecBuilder crane(Object object);
+
/**
* Add a signatureFix layer. Reads the @extras/record_signatures.json" file in a jar file such as yarn.
*/
diff --git a/src/main/java/net/fabricmc/loom/build/JarRemapper.java b/src/main/java/net/fabricmc/loom/build/JarRemapper.java
index 47158bc0..5996401b 100644
--- a/src/main/java/net/fabricmc/loom/build/JarRemapper.java
+++ b/src/main/java/net/fabricmc/loom/build/JarRemapper.java
@@ -35,15 +35,17 @@ import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
+import dev.architectury.tinyremapper.IMappingProvider;
+import dev.architectury.tinyremapper.InputTag;
+import dev.architectury.tinyremapper.OutputConsumerPath;
+import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Action;
+import org.gradle.api.Project;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.loom.util.CloseableList;
+import net.fabricmc.loom.util.LoggerFilter;
import net.fabricmc.stitch.util.Pair;
-import net.fabricmc.tinyremapper.IMappingProvider;
-import net.fabricmc.tinyremapper.InputTag;
-import net.fabricmc.tinyremapper.OutputConsumerPath;
-import net.fabricmc.tinyremapper.TinyRemapper;
public class JarRemapper {
private final List<IMappingProvider> mappingProviders = new ArrayList<>();
@@ -65,8 +67,11 @@ public class JarRemapper {
return data;
}
- public void remap() throws IOException {
+ public void remap(Project project) throws IOException {
+ LoggerFilter.replaceSystemOut();
TinyRemapper.Builder remapperBuilder = TinyRemapper.newRemapper();
+ remapperBuilder.logger(project.getLogger()::lifecycle);
+ remapperBuilder.logUnknownInvokeDynamic(false);
mappingProviders.forEach(remapperBuilder::withMappings);
if (remapOptions != null) {
@@ -79,7 +84,7 @@ public class JarRemapper {
Path[] remapClasspath = classPath.stream()
.filter(path ->
- remapData.stream().noneMatch(remapData -> remapData.input.equals(path))
+ remapData.stream().noneMatch(remapData -> remapData.input.toString().equals(path.toString()))
)
.toArray(Path[]::new);
@@ -88,13 +93,28 @@ public class JarRemapper {
for (RemapData data : remapData) {
InputTag tag = remapper.createInputTag();
data.tag = tag;
- remapper.readInputsAsync(tag, data.input);
+ project.getLogger().info(":remapper input -> " + data.input.getFileName().toString());
+
+ try {
+ remapper.readInputsAsync(tag, data.input);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to read remapper input " + data.input.getFileName().toString(), e);
+ }
}
//noinspection MismatchedQueryAndUpdateOfCollection
try (CloseableList<OutputConsumerPath> outputConsumers = new CloseableList<>()) {
for (RemapData data : remapData) {
- OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(data.output).build();
+ OutputConsumerPath outputConsumer;
+ project.getLogger().info(":remapper output -> " + data.output.getFileName().toString());
+
+ try {
+ Files.deleteIfExists(data.output);
+ outputConsumer = new OutputConsumerPath.Builder(data.output).build();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to create remapper output " + data.output.getFileName().toString(), e);
+ }
+
outputConsumers.add(outputConsumer);
outputConsumer.addNonClassFiles(data.input);
diff --git a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java b/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java
index 4d1ba261..580d5659 100644
--- a/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java
+++ b/src/main/java/net/fabricmc/loom/build/MixinRefmapHelper.java
@@ -60,7 +60,7 @@ public final class MixinRefmapHelper {
MixinExtension mixin = LoomGradleExtension.get(project).getMixin();
File output = outputPath.toFile();
- Collection<String> allMixinConfigs = getMixinConfigurationFiles(readFabricModJson(output));
+ Collection<String> allMixinConfigs = LoomGradleExtension.get(project).isForge() ? LoomGradleExtension.get(project).getForge().getMixinConfigs().get() : getMixinConfigurationFiles(readFabricModJson(output));
return mixin.getMixinSourceSetsStream().map(sourceSet -> {
MixinExtension.MixinInformationContainer container = Objects.requireNonNull(
diff --git a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
index b6150fb0..76475caa 100644
--- a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
+++ b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.function.Supplier;
import java.util.zip.ZipFile;
+import com.google.common.base.Suppliers;
import com.google.common.io.Files;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
@@ -88,10 +89,12 @@ public class ModCompileRemapper {
for (ResolvedArtifact artifact : sourceConfig.getResolvedConfiguration().getResolvedArtifacts()) {
String group = replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getGroup(), () -> MISSING_GROUP);
- String name = artifact.getModuleVersion().getId().getName();
- String version = replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.truncatedSha256(artifact.getFile()));
+ // Awful fix for https://github.com/architectury/architectury-loom/issues/42 for now
+ Supplier<String> checksum = Suppliers.memoize(() -> Checksum.truncatedSha256(artifact.getFile()));
+ String name = extension.isForgeAndOfficial() ? "B" + checksum.get() : artifact.getModuleVersion().getId().getName();
+ String version = extension.isForgeAndOfficial() ? "B" + checksum.get() : replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.truncatedSha256(artifact.getFile()));
- if (!isFabricMod(logger, artifact.getFile(), artifact.getId())) {
+ if (!shouldRemapMod(logger, artifact.getFile(), artifact.getId(), extension.isForge(), sourceConfig.getName())) {
addToRegularCompile(project, regularConfig, artifact);
continue;
}
@@ -118,7 +121,7 @@ public class ModCompileRemapper {
// Create a mod dependency for each file in the file collection
for (File artifact : files) {
- if (!isFabricMod(logger, artifact, artifact.getName())) {
+ if (!shouldRemapMod(logger, artifact, artifact.getName(), extension.isForge(), sourceConfig.getName())) {
dependencies.add(regularConfig.getName(), project.files(artifact));
continue;
}
@@ -150,6 +153,7 @@ public class ModCompileRemapper {
// Add all of the remapped mods onto the config
for (ModDependencyInfo info : modDependencies) {
+ project.getLogger().info(":adding " + info.toString() + " into " + info.targetConfig.getName());
project.getDependencies().add(info.targetConfig.getName(), info.getRemappedNotation());
}
@@ -169,13 +173,28 @@ public class ModCompileRemapper {
/**
* Checks if an artifact is a fabric mod, according to the presence of a fabric.mod.json.
*/
- private static boolean isFabricMod(Logger logger, File artifact, Object id) {
+ private static boolean shouldRemapMod(Logger logger, File artifact, Object id, boolean forge, String config) {
try (ZipFile zipFile = new ZipFile(artifact)) {
- if (zipFile.getEntry("fabric.mod.json") != null) {
- logger.info("Found Fabric mod in modCompile: {}", id);
+ if (zipFile.getEntry("architectury.common.marker") != null) {
+ logger.info("Found architectury common mod in " + config + ": {}", id);
return true;
}
+ if (forge) {
+ if (zipFile.getEntry("META-INF/mods.toml") != null) {
+ logger.info("Found Forge mod in " + config + ": {}", id);
+ return true;
+ }
+
+ logger.lifecycle(":could not find forge mod in " + config + " but forcing: {}", id);
+ return true;
+ } else {
+ if (zipFile.getEntry("fabric.mod.json") != null) {
+ logger.info("Found Fabric mod in " + config + ": {}", id);
+ return true;
+ }
+ }
+
return false;
} catch (IOException e) {
return false;
diff --git a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
index f87ccc21..33fec4e6 100644
--- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
+++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
@@ -26,6 +26,7 @@ package net.fabricmc.loom.build.mixin;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -81,8 +82,9 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
try {
LoomGradleExtension loom = LoomGradleExtension.get(project);
String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get();
+ Path mappings = loom.isForge() ? loom.getMappingsProvider().mixinTinyMappingsWithSrg : loom.getMappingsProvider().tinyMappings;
Map<String, String> args = new HashMap<>() {{
- put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, loom.getMappingsProvider().tinyMappings.toFile().getCanonicalPath());
+ put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, mappings.toFile().getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, loom.getNextMixinMappings().getCanonicalPath());
put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName));
put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary");
@@ -109,6 +111,10 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
configs.getByName(Constants.Configurations.MAPPINGS_FINAL)
);
+ if (extension.isForge()) {
+ processorConfig.extendsFrom(configs.getByName(Constants.Configurations.FORGE_NAMED));
+ }
+
// Add Mixin and mixin extensions (fabric-mixin-compile-extensions pulls mixin itself too)
project.getDependencies().add(processorConfig.getName(),
Constants.Dependencies.MIXIN_COMPILE_EXTENSIONS + Constants.Dependencies.Versions.MIXIN_COMPILE_EXTENSIONS);
diff --git a/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java b/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java
new file mode 100644
index 00000000..8a305d49
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/build/nesting/EmptyNestedJarProvider.java
@@ -0,0 +1,38 @@
+/*
+ * 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.build.nesting;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+
+public enum EmptyNestedJarProvider implements NestedJarProvider {
+ INSTANCE;
+
+ @Override
+ public Collection<File> provide() {
+ return Collections.emptyList();
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
index f99d7216..14c4c720 100644
--- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
@@ -42,8 +42,16 @@ 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.extension.MixinExtension;
+import net.fabricmc.loom.task.GenVsCodeProjectTask;
import net.fabricmc.loom.util.Constants;
public final class CompileConfiguration {
@@ -53,13 +61,59 @@ public final class CompileConfiguration {
public static void setupConfigurations(Project project) {
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);
+ extension.createLazyConfiguration(Constants.Configurations.FORGE_NAMED).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.FORGE_EXTRA).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.MCP_CONFIG).configure(configuration -> configuration.setTransitive(false));
+ extension.createLazyConfiguration(Constants.Configurations.FORGE_RUNTIME_LIBRARY);
+
+ extendsFrom(Constants.Configurations.MINECRAFT_DEPENDENCIES, Constants.Configurations.FORGE_DEPENDENCIES, project);
+
+ extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.FORGE_DEPENDENCIES, project);
+ extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.MINECRAFT_DEPENDENCIES, project);
+ extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.FORGE_EXTRA, project);
+ extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.MINECRAFT_NAMED, project);
+ extendsFrom(Constants.Configurations.FORGE_RUNTIME_LIBRARY, Constants.Configurations.FORGE_NAMED, project);
+ // Include any user-defined libraries on the runtime CP.
+ // (All the other superconfigurations are already on there.)
+ extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_RUNTIME_LIBRARY, 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);
+ extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_EXTRA, project);
+ extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.FORGE_EXTRA, project);
+ }
+
+ if (extension.supportsInclude()) {
+ extension.createLazyConfiguration(Constants.Configurations.INCLUDE).configure(configuration -> configuration.setTransitive(false)); // Dont get transitive deps
+ }
+
extension.createLazyConfiguration(Constants.Configurations.MAPPING_CONSTANTS);
extension.createLazyConfiguration(Constants.Configurations.NAMED_ELEMENTS).configure(configuration -> {
configuration.setCanBeConsumed(true);
@@ -131,7 +185,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);
@@ -141,6 +211,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 74642f79..c50102d7 100644
--- a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java
@@ -202,7 +202,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 eeeb4d20..edfa2ca6 100644
--- a/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/JarManifestConfiguration.java
@@ -28,6 +28,7 @@ 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.api.artifacts.Dependency;
import org.gradle.util.GradleVersion;
@@ -35,7 +36,6 @@ 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 29ae1d03..af080877 100644
--- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
+++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java
@@ -77,7 +77,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;
@@ -135,16 +135,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 mappingsIdentifier = mappingsProvider.mappingsIdentifier();
+ String platformSuffix = extension.isForge() ? "_forge" : "";
+ String mappingsIdentifier = mappingsProvider.mappingsIdentifier() + 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);
@@ -165,10 +166,10 @@ public class LoomDependencyManager {
}
}
}
- }
- if (extension.getInstallerData() == null) {
- project.getLogger().warn("fabric-installer.json not found in classpath!");
+ if (extension.getInstallerData() == null) {
+ project.getLogger().warn("fabric-installer.json not found in classpath!");
+ }
}
ModCompileRemapper.remapDependencies(project, mappingsIdentifier, 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 29c312ea..ef84fda0 100644
--- a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java
+++ b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java
@@ -71,6 +71,7 @@ public final class MavenPublication {
PublishingExtension mavenPublish = p.getExtensions().findByType(PublishingExtension.class);
if (mavenPublish != null) {
+ p.getLogger().info("Processing maven publication for project [" + p.getName() + "] of " + configurationName);
processEntry(project, scope, config, mavenPublish, reportedDeprecation);
}
});
@@ -97,12 +98,13 @@ public final class MavenPublication {
if (hasSoftwareComponent(publication) || EXCLUDED_PUBLICATIONS.contains(publication)) {
continue;
- } else if (!reportedDeprecation.get()) {
+ } else if (!reportedDeprecation.get() && !LoomGradleExtension.get(project).isForge()) {
DeprecationHelper deprecationHelper = LoomGradleExtension.get(project).getDeprecationHelper();
deprecationHelper.warn("Loom is applying dependency data manually to publications instead of using a software component (from(components[\"java\"])). This is deprecated and will be removed in Loom 0.12.");
reportedDeprecation.set(true);
}
+ project.getLogger().info("Processing maven publication [" + publication.getName() + "]");
mavenPublication.pom((pom) -> pom.withXml((xml) -> {
Node dependencies = GroovyXmlUtil.getOrCreateNode(xml.asNode(), "dependencies");
Set<String> foundArtifacts = new HashSet<>();
@@ -118,9 +120,12 @@ public final class MavenPublication {
for (Dependency dependency : config.getAllDependencies()) {
if (foundArtifacts.contains(dependency.getGroup() + ":" + dependency.getName())) {
+ project.getLogger().info("Found inserted artifact " + dependency.getGroup() + ":" + dependency.getName());
continue;
}
+ project.getLogger().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 efbf5a1d..baf33086 100644
--- a/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/RemapConfiguration.java
@@ -25,8 +25,10 @@
package net.fabricmc.loom.configuration;
import java.io.IOException;
+import java.util.Set;
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;
@@ -37,6 +39,7 @@ import org.gradle.api.artifacts.PublishArtifact;
import org.gradle.api.artifacts.dsl.ArtifactHandler;
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;
@@ -46,7 +49,9 @@ import net.fabricmc.loom.task.AbstractLoomTask;
import net.fabricmc.loom.task.RemapAllSourcesTask;
import net.fabricmc.loom.task.RemapJarTask;
import net.fabricmc.loom.task.RemapSourcesJarTask;
+import net.fabricmc.loom.util.PropertyUtil;
import net.fabricmc.loom.util.SourceRemapper;
+import net.fabricmc.loom.util.aw2at.Aw2At;
public class RemapConfiguration {
private static final String DEFAULT_JAR_TASK_NAME = JavaPlugin.JAR_TASK_NAME;
@@ -81,6 +86,9 @@ public class RemapConfiguration {
// It doesn't seem to hurt, but I'm not sure if the file-level duplicates cause issues.
Configuration configuration = project.getConfigurations().getByName(JavaPlugin.SOURCES_ELEMENTS_CONFIGURATION_NAME);
configuration.getArtifacts().removeIf(a -> a != artifact && artifact.getFile().equals(a.getFile()));
+ // Architectury Loom Patch
+ // Since we make sourcesJar and remapSourcesJar output files separate, we have to remove the dev sources jar file here
+ configuration.getArtifacts().removeIf(a -> a != artifact && task.getInput().get().getAsFile().equals(a.getFile()));
});
// Remove -dev jars from the default jar task
@@ -114,12 +122,31 @@ public class RemapConfiguration {
remapJarTask.getInput().convention(jarTask.getArchiveFile());
}
+ if (extension.isForge()) {
+ Set<String> mixinConfigs = PropertyUtil.getAndFinalize(extension.getForge().getMixinConfigs());
+
+ if (!mixinConfigs.isEmpty()) {
+ ((Jar) jarTask).manifest(manifest -> {
+ manifest.attributes(ImmutableMap.of("MixinConfigs", String.join(",", mixinConfigs)));
+ });
+ }
+ }
+
if (isDefaultRemap) {
extension.getUnmappedModCollection().from(jarTask);
remapJarTask.getAddNestedDependencies().convention(true);
remapJarTask.getRemapAccessWidener().convention(true);
project.getArtifacts().add("archives", remapJarTask);
+
+ if (extension.isForge()) {
+ boolean convertAws = PropertyUtil.getAndFinalize(extension.getForge().getConvertAccessWideners());
+
+ if (convertAws) {
+ Aw2At.setup(project, remapJarTask);
+ remapJarTask.getRemapAccessWidener().set(false);
+ }
+ }
}
remapJarTask.dependsOn(jarTask);
@@ -127,7 +154,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);
}
});
@@ -155,7 +182,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);
}
@@ -179,9 +206,11 @@ public class RemapConfiguration {
RemapSourcesJarTask remapSourcesJarTask = (RemapSourcesJarTask) project.getTasks().findByName(remapSourcesJarTaskName);
Preconditions.checkNotNull(remapSourcesJarTask, "Could not find " + remapSourcesJarTaskName + " in " + project.getName());
+ remapSourcesJarTask.getOutput().convention(sourcesTask.getArchiveFile().get());
+ String sourcesTaskClassifer = sourcesTask.getArchiveClassifier().get();
+ sourcesTask.getArchiveClassifier().set(sourcesTaskClassifer == null ? "dev" : sourcesTaskClassifer + "-dev");
remapSourcesJarTask.getInput().convention(sourcesTask.getArchiveFile());
- remapSourcesJarTask.getOutput().convention(sourcesTask.getArchiveFile());
- remapSourcesJarTask.dependsOn(project.getTasks().getByName(sourcesJarTaskName));
+ 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/AccessWidenerFile.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java
index 893a1611..4961e71d 100644
--- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java
+++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerFile.java
@@ -54,6 +54,44 @@ public record AccessWidenerFile(
}
if (modJsonBytes == null) {
+ if (ZipUtils.contains(modJarPath, "architectury.common.json")) {
+ String awPath = null;
+ byte[] commonJsonBytes;
+
+ try {
+ commonJsonBytes = ZipUtils.unpackNullable(modJarPath, "architectury.common.json");
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to read access-widener file from: " + modJarPath.toAbsolutePath(), e);
+ }
+
+ if (commonJsonBytes != null) {
+ JsonObject jsonObject = new Gson().fromJson(new String(commonJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
+
+ if (jsonObject.has("accessWidener")) {
+ awPath = jsonObject.get("accessWidener").getAsString();
+ } else {
+ throw new IllegalArgumentException("The architectury.common.json file does not contain an accessWidener field.");
+ }
+ } else {
+ // ???????????
+ throw new IllegalArgumentException("The architectury.common.json file does not exist.");
+ }
+
+ byte[] content;
+
+ try {
+ content = ZipUtils.unpack(modJarPath, awPath);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Could not find access widener file (%s) defined in the architectury.common.json file of %s".formatted(awPath, modJarPath.toAbsolutePath()), e);
+ }
+
+ return new AccessWidenerFile(
+ awPath,
+ modJarPath.getFileName().toString(),
+ content
+ );
+ }
+
return null;
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java
index d5f234bd..9ab13264 100644
--- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java
+++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java
@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Set;
import com.google.common.base.Preconditions;
+import dev.architectury.tinyremapper.TinyRemapper;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.FileCollectionDependency;
@@ -51,7 +52,6 @@ import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.TinyRemapperHelper;
-import net.fabricmc.tinyremapper.TinyRemapper;
/**
* Applies transitive access wideners that are inherited from mod and api dependencies.
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 3e23dec3..0d180e6b 100644
--- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
+++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java
@@ -29,13 +29,16 @@ import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Consumer;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
@@ -50,11 +53,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 List<String> vmArgs = new ArrayList<>();
public List<String> programArgs = new ArrayList<>();
+ public List<String> vscodeBeforeRun = new ArrayList<>();
+ public final Map<String, String> envVariables = new HashMap<>();
public SourceSet sourceSet;
public Element genRuns(Element doc) {
@@ -73,6 +79,14 @@ public class RunConfig {
this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", joinArguments(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;
}
@@ -106,6 +120,7 @@ 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() ? "" : StringUtils.removePrefix(project.getPath(), ":");
runConfig.mainClass = "net.fabricmc.devlaunchinjector.Main";
runConfig.vmArgs.add("-Dfabric.dli.config=" + encodeEscaped(extension.getFiles().getDevLauncherConfig().getAbsolutePath()));
@@ -123,6 +138,7 @@ public class RunConfig {
}
public static RunConfig runConfig(Project project, RunConfigSettings settings) {
+ settings.evaluateNow();
LoomGradleExtension extension = LoomGradleExtension.get(project);
String name = settings.getName();
@@ -156,6 +172,7 @@ public class RunConfig {
}
RunConfig runConfig = new RunConfig();
+ runConfig.envVariables.putAll(settings.envVariables);
runConfig.configName = configName;
populate(project, extension, runConfig, environment);
runConfig.ideaModuleName = getIdeaModuleName(project, sourceSet);
@@ -168,6 +185,10 @@ public class RunConfig {
runConfig.vmArgs.addAll(settings.getVmArgs());
runConfig.vmArgs.add("-Dfabric.dli.main=" + getMainClass(environment, extension, defaultMain));
+ for (Consumer<RunConfig> consumer : extension.getSettingsPostEdit()) {
+ consumer.accept(runConfig);
+ }
+
return runConfig;
}
@@ -196,6 +217,25 @@ public class RunConfig {
dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", joinArguments(programArgs).replaceAll("\"", "&quot;"));
dummyConfig = dummyConfig.replace("%VM_ARGS%", joinArguments(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;
}
@@ -216,7 +256,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 a7a1c428..793f5f1a 100644
--- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java
+++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java
@@ -28,6 +28,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -36,6 +37,7 @@ import org.gradle.api.Named;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet;
+import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Constants;
@@ -96,6 +98,8 @@ public final class RunConfigSettings implements Named {
private final Project project;
private final LoomGradleExtension extension;
+ public final Map<String, String> envVariables = new HashMap<>();
+ private List<Runnable> evaluateLater = new ArrayList<>();
public RunConfigSettings(Project project, String baseName) {
this.baseName = baseName;
@@ -107,6 +111,20 @@ public final class RunConfigSettings implements Named {
runDir("run");
}
+ @ApiStatus.Internal
+ public void evaluateLater(Runnable runnable) {
+ this.evaluateLater.add(runnable);
+ }
+
+ @ApiStatus.Internal
+ public void evaluateNow() {
+ for (Runnable runnable : this.evaluateLater) {
+ runnable.run();
+ }
+
+ this.evaluateLater.clear();
+ }
+
public Project getProject() {
return project;
}
@@ -262,7 +280,7 @@ public final class RunConfigSettings implements Named {
public void client() {
startFirstThread();
environment("client");
- defaultMainClass(Constants.Knot.KNOT_CLIENT);
+ defaultMainClass(getExtension().isForge() ? Constants.Forge.LAUNCH_TESTING : Constants.Knot.KNOT_CLIENT);
}
/**
@@ -271,7 +289,15 @@ public final class RunConfigSettings implements Named {
public void server() {
programArg("nogui");
environment("server");
- defaultMainClass(Constants.Knot.KNOT_SERVER);
+ defaultMainClass(getExtension().isForge() ? Constants.Forge.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER);
+ }
+
+ /**
+ * Configure run config with the default server options.
+ */
+ public void data() {
+ environment("data");
+ defaultMainClass(getExtension().isForge() ? Constants.Forge.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER);
}
/**
diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java b/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java
index 8120a63f..34c7cff1 100644
--- a/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java
+++ b/src/main/java/net/fabricmc/loom/configuration/ide/SetupIntelijRunConfigs.java
@@ -44,13 +44,13 @@ public class SetupIntelijRunConfigs {
}
try {
- generate(project);
+ generate(project, false);
} catch (IOException e) {
throw new RuntimeException("Failed to generate run configs", e);
}
}
- private static void generate(Project project) throws IOException {
+ public static void generate(Project project, boolean override) throws IOException {
Project rootProject = project.getRootProject();
LoomGradleExtension extension = LoomGradleExtension.get(project);
@@ -80,6 +80,10 @@ public class SetupIntelijRunConfigs {
File runConfigs = new File(runConfigsDir, name + projectPath + ".xml");
String runConfigXml = config.fromDummy("idea_run_config_template.xml", true, project);
+ if (runConfigs.exists() && override) {
+ runConfigs.delete();
+ }
+
if (!runConfigs.exists()) {
FileUtils.writeStringToFile(runConfigs, runConfigXml, StandardCharsets.UTF_8);
}
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..0ac814b5
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/launch/LaunchProviderSettings.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+import org.jetbrains.annotations.ApiStatus;
+
+public class LaunchProviderSettings implements Named {
+ private final String name;
+ private List<Map.Entry<String, String>> properties = new ArrayList<>();
+ private List<String> arguments = new ArrayList<>();
+ private List<Runnable> evaluateLater = new ArrayList<>();
+
+ public LaunchProviderSettings(Project project, String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @ApiStatus.Internal
+ public void evaluateLater(Runnable runnable) {
+ this.evaluateLater.add(runnable);
+ }
+
+ @ApiStatus.Internal
+ public void evaluateNow() {
+ for (Runnable runnable : this.evaluateLater) {
+ runnable.run();
+ }
+
+ this.evaluateLater.clear();
+ }
+
+ 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 bc888d1f..0c28cac9 100644
--- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
+++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java
@@ -24,6 +24,8 @@
package net.fabricmc.loom.configuration.mods;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -31,10 +33,20 @@ import java.nio.file.Files;
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.Manifest;
+import java.util.stream.Stream;
+import com.google.common.base.Stopwatch;
import com.google.gson.JsonObject;
+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.objectweb.asm.commons.Remapper;
@@ -48,12 +60,13 @@ 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.FileSystemUtil;
+import net.fabricmc.loom.util.LoggerFilter;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipUtils;
-import net.fabricmc.tinyremapper.InputTag;
-import net.fabricmc.tinyremapper.NonClassCopyMode;
-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.mappingio.tree.MemoryMappingTree;
public class ModProcessor {
private static final String fromM = MappingsNamespace.INTERMEDIARY.toString();
@@ -103,6 +116,7 @@ public class ModProcessor {
}
private void stripNestedJars(File file) {
+ if (!ZipUtils.contains(file.toPath(), "fabric.mod.json")) return;
// Strip out all contained jar info as we dont want loader to try and load the jars contained in dev.
try {
ZipUtils.transformJson(JsonObject.class, file.toPath(), Map.of("fabric.mod.json", json -> {
@@ -136,19 +150,30 @@ public class ModProcessor {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final MinecraftMappedProvider mappedProvider = extension.getMinecraftMappedProvider();
final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
+ String fromM = extension.isForge() ? MappingsNamespace.SRG.toString() : MappingsNamespace.INTERMEDIARY.toString();
+ String toM = MappingsNamespace.NAMED.toString();
- Path intermediaryJar = mappedProvider.getIntermediaryJar().toPath();
+ Path intermediaryJar = 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);
+ Stopwatch stopwatch = Stopwatch.createStarted();
project.getLogger().lifecycle(":remapping " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ")");
+ MemoryMappingTree mappings = (fromM.equals("srg") || toM.equals("srg")) && extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings();
+ LoggerFilter.replaceSystemOut();
final TinyRemapper remapper = TinyRemapper.newRemapper()
- .withMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false))
+ .logger(project.getLogger()::lifecycle)
+ .logUnknownInvokeDynamic(false)
+ .withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false))
.renameInvalidLocals(false)
.build();
remapper.readClassPathAsync(intermediaryJar);
+
+ if (extension.isForgeAndNotOfficial()) {
+ remapper.readClassPathAsync(mappedProvider.getForgeSrgJar().toPath());
+ }
remapper.readClassPathAsync(mcDeps);
final Map<ModDependencyInfo, InputTag> tagMap = new HashMap<>();
@@ -200,6 +225,8 @@ 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();
byte[] accessWidener = accessWidenerMap.get(info);
@@ -211,7 +238,63 @@ public class ModProcessor {
stripNestedJars(info.getRemappedOutput());
+ if (extension.isForge()) {
+ AtRemapper.remap(project.getLogger(), info.getRemappedOutput().toPath(), mappings);
+ CoreModClassRemapper.remapJar(info.getRemappedOutput().toPath(), mappings, project.getLogger());
+
+ ZipUtils.transform(info.getRemappedOutput().toPath(), Map.of("META-INF/MANIFEST.MF", bytes -> {
+ Manifest manifest = new Manifest(new ByteArrayInputStream(bytes));
+ fixManifest(manifest);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ manifest.write(out);
+ return out.toByteArray();
+ }));
+
+ try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(info.getRemappedOutput().toPath(), false);
+ Stream<Path> walk = Files.walk(fs.get().getPath("/"))) {
+ List<Path> filesToRemove = new ArrayList<>();
+ Iterator<Path> iterator = walk.iterator();
+
+ while (iterator.hasNext()) {
+ Path fsPath = iterator.next();
+ if (!Files.isRegularFile(fsPath)) continue;
+ String fileName = fsPath.toString();
+
+ if (fileName.toLowerCase(Locale.ROOT).endsWith(".rsa") || fileName.toLowerCase(Locale.ROOT).endsWith(".sf")) {
+ if (fileName.startsWith("META-INF")) {
+ filesToRemove.add(fsPath);
+ }
+ }
+ }
+
+ for (Path fileToRemove : filesToRemove) {
+ Files.delete(fileToRemove);
+ }
+ }
+ }
+
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();
+ }
+ }
}
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 fc8aa863..744bc53e 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");
invalidateJar();
@@ -66,7 +68,7 @@ public class MinecraftProcessedProvider extends MinecraftMappedProvider {
}
getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED,
- getProject().getDependencies().module("net.minecraft:minecraft-" + projectMappedClassifier + ":" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()));
+ getProject().getDependencies().module("net.minecraft:" + minecraftProvider.getJarPrefix() + "minecraft-" + projectMappedClassifier + ":" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()));
}
private void invalidateJar() {
@@ -86,7 +88,7 @@ public class MinecraftProcessedProvider extends MinecraftMappedProvider {
super.initFiles(minecraftProvider, mappingsProvider);
projectMappedJar = new File(getDirectories().getRootProjectPersistentCache(), getMinecraftProvider().minecraftVersion() + "/"
- + getExtension().getMappingsProvider().mappingsIdentifier() + "/minecraft-" + projectMappedClassifier + ".jar");
+ + getExtension().getMappingsProvider().mappingsIdentifier() + "/" + minecraftProvider.getJarPrefix() + "minecraft-" + projectMappedClassifier + ".jar");
}
@Override
diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java b/src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java
index 10aa256a..5f6d0377 100644
--- a/src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java
+++ b/src/main/java/net/fabricmc/loom/configuration/processors/dependency/ModDependencyInfo.java
@@ -192,7 +192,18 @@ public class ModDependencyInfo {
}
private static AccessWidenerData tryReadAccessWidenerData(Path inputJar) throws IOException {
- byte[] modJsonBytes = ZipUtils.unpack(inputJar, "fabric.mod.json");
+ byte[] modJsonBytes = ZipUtils.unpackNullable(inputJar, "fabric.mod.json");
+
+ if (modJsonBytes == null) {
+ modJsonBytes = ZipUtils.unpackNullable(inputJar, "architectury.common.json");
+
+ if (modJsonBytes == null) {
+ // No access widener data
+ // We can just ignore in architectury
+ return null;
+ }
+ }
+
JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
if (!jsonObject.has("accessWidener")) {
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 f033750e..2ea5c812 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java
@@ -34,6 +34,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -45,7 +46,9 @@ 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;
+import net.fabricmc.loom.util.PropertyUtil;
public class LaunchProvider extends DependencyProvider {
public LaunchProvider(Project project) {
@@ -61,12 +64,65 @@ public class LaunchProvider extends DependencyProvider {
.property("log4j2.formatMsgNoLookups", "true")
.property("client", "java.library.path", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath())
- .property("client", "org.lwjgl.librarypath", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath())
+ .property("client", "org.lwjgl.librarypath", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath());
+
+ if (!getExtension().isForge()) {
+ launchConfig
+ .argument("client", "--assetIndex")
+ .argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()))
+ .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("data", "--all")
+ .argument("data", "--mod")
+ .argument("data", String.join(",", getExtension().getForge().getDataGenMods()))
+ .argument("data", "--output")
+ .argument("data", getProject().file("src/generated/resources").getAbsolutePath())
+
+ .property("mixin.env.remapRefMap", "true");
+
+ if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) {
+ launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingsProvider().mixinTinyMappingsWithSrg.toAbsolutePath().toString());
+ } else {
+ launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.toAbsolutePath().toString());
+ }
+
+ Set<String> mixinConfigs = PropertyUtil.getAndFinalize(getExtension().getForge().getMixinConfigs());
+
+ if (!mixinConfigs.isEmpty()) {
+ for (String config : mixinConfigs) {
+ launchConfig.argument("-mixin.config");
+ launchConfig.argument(config);
+ }
+ }
+ }
+
+ addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES);
+ 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, Constants.Configurations.FORGE_EXTRA);
+ addDependency(Constants.Dependencies.JAVAX_ANNOTATIONS + Constants.Dependencies.Versions.JAVAX_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME);
+ }
+
+ for (LaunchProviderSettings settings : getExtension().getLaunchConfigs()) {
+ settings.evaluateNow();
- .argument("client", "--assetIndex")
- .argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion()))
- .argument("client", "--assetsDir")
- .argument("client", new File(getDirectories().getUserCache(), "assets").getAbsolutePath());
+ 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());
+ }
+ }
final boolean plainConsole = getProject().getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain;
final boolean ansiSupportedIDE = new File(getProject().getRootDir(), ".vscode").exists()
@@ -81,10 +137,6 @@ public class LaunchProvider extends DependencyProvider {
writeLog4jConfig();
FileUtils.writeStringToFile(getDirectories().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8);
- addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES);
- 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);
-
postPopulationScheduler.accept(this::writeRemapClassPath);
}
@@ -124,6 +176,10 @@ public class LaunchProvider extends DependencyProvider {
remapClasspath.add(getExtension().getMinecraftMappedProvider().getIntermediaryJar());
+ if (getExtension().isForgeAndNotOfficial()) {
+ 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 4cda3be7..c0ef6acd 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java
@@ -37,6 +37,7 @@ import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import com.google.common.base.Stopwatch;
import com.google.common.io.Files;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
@@ -51,6 +52,7 @@ import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.MirrorUtil;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.HashedDownloadUtil;
+import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.stitch.merge.JarMerger;
public class MinecraftProviderImpl extends DependencyProvider implements MinecraftProvider {
@@ -61,14 +63,16 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
private File workingDir;
private File minecraftJson;
- private File minecraftClientJar;
+ public File minecraftClientJar;
// Note this will be the boostrap jar starting with 21w39a
- private File minecraftServerJar;
+ public File minecraftServerJar;
// The extracted server jar from the boostrap, only exists in >=21w39a
- private File minecraftExtractedServerJar;
+ public File minecraftExtractedServerJar;
+ private Boolean isNewerThan21w39a;
private File minecraftMergedJar;
private File versionManifestJson;
private File experimentalVersionsJson;
+ private String jarPrefix = "";
public MinecraftProviderImpl(Project project) {
super(project);
@@ -78,6 +82,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();
@@ -130,6 +138,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;
@@ -256,11 +272,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, getServerJarToMerge(logger), minecraftMergedJar)) {
jarMerger.enableSyntheticParamsOffset();
jarMerger.merge();
}
+
+ logger.info(":merged jars in " + stopwatch);
}
private File getServerJarToMerge(Logger logger) throws IOException {
@@ -315,6 +334,26 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
}
}
+ public File getMinecraftServerJar() {
+ if (isNewerThan21w39a()) {
+ try {
+ return getServerJarToMerge(getProject().getLogger());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return minecraftServerJar;
+ }
+
+ public boolean isNewerThan21w39a() {
+ if (isNewerThan21w39a != null) {
+ return isNewerThan21w39a;
+ }
+
+ return isNewerThan21w39a = ZipUtils.contains(minecraftServerJar.toPath(), "META-INF/versions.list");
+ }
+
public File getMergedJar() {
return minecraftMergedJar;
}
@@ -364,6 +403,14 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra
return libraryProvider;
}
+ public String getJarPrefix() {
+ return jarPrefix;
+ }
+
+ public void setJarPrefix(String jarSuffix) {
+ this.jarPrefix = 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..b2ebfa71
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java
@@ -0,0 +1,282 @@
+/*
+ * 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.StringWriter;
+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.base.Stopwatch;
+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.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.api.mappings.layered.MappingsNamespace;
+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.mappingio.MappingReader;
+import net.fabricmc.mappingio.format.Tiny2Writer;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MappingTreeView;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+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
+ protected String createMappingsIdentifier(String mappingsName, String version, String classifier) {
+ return super.createMappingsIdentifier(mappingsName, version, classifier) + "-forge-" + getExtension().getForgeProvider().getVersion().getCombined();
+ }
+
+ @Override
+ public void manipulateMappings(Path mappingsJar) throws IOException {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ LoomGradleExtension extension = getExtension();
+ this.rawTinyMappings = tinyMappings;
+ this.rawTinyMappingsWithSrg = tinyMappingsWithSrg;
+ String mappingsJarName = mappingsJar.getFileName().toString();
+
+ if (getExtension().shouldGenerateSrgTiny()) {
+ if (Files.notExists(rawTinyMappingsWithSrg) || isRefreshDeps()) {
+ // Merge tiny mappings with srg
+ SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), rawTinyMappings, rawTinyMappingsWithSrg, true);
+ }
+ }
+
+ tinyMappings = mappingsWorkingDir().resolve("mappings-updated.tiny");
+ tinyMappingsWithSrg = mappingsWorkingDir().resolve("mappings-srg-updated.tiny");
+
+ try {
+ updateFieldMigration();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ getProject().getLogger().info(":migrated srg fields in " + stopwatch.stop());
+ }
+
+ 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);
+ }
+
+ if (!Files.exists(tinyMappings)) {
+ Table<String, String, String> fieldDescriptorMap = HashBasedTable.create();
+
+ for (Map.Entry<FieldMember, String> entry : migratedFields) {
+ fieldDescriptorMap.put(entry.getKey().owner, entry.getKey().field, entry.getValue());
+ }
+
+ MemoryMappingTree mappings = new MemoryMappingTree();
+
+ try (BufferedReader reader = Files.newBufferedReader(rawTinyMappings)) {
+ MappingReader.read(reader, mappings);
+
+ for (MappingTree.ClassMapping classDef : new ArrayList<>(mappings.getClasses())) {
+ Map<String, String> row = fieldDescriptorMap.row(classDef.getName(MappingsNamespace.INTERMEDIARY.toString()));
+
+ if (!row.isEmpty()) {
+ for (MappingTree.FieldMapping fieldDef : new ArrayList<>(classDef.getFields())) {
+ String newDescriptor = row.get(fieldDef.getName(MappingsNamespace.INTERMEDIARY.toString()));
+
+ if (newDescriptor != null) {
+ fieldDef.setSrcDesc(mappings.mapDesc(newDescriptor, mappings.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString()), MappingTreeView.SRC_NAMESPACE_ID));
+ }
+ }
+ }
+ }
+ }
+
+ StringWriter stringWriter = new StringWriter();
+ Tiny2Writer tiny2Writer = new Tiny2Writer(stringWriter, false);
+ mappings.accept(tiny2Writer);
+ Files.writeString(tinyMappings, stringWriter.toString(), 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.Delegate 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)) {
+ MemoryMappingTree mappings = new MemoryMappingTree();
+ MappingReader.read(reader, mappings);
+ Map<String, String> srgToIntermediary = new HashMap<>();
+
+ for (MappingTree.ClassMapping aClass : mappings.getClasses()) {
+ srgToIntermediary.put(aClass.getName("srg"), aClass.getName("intermediary"));
+ }
+
+ for (MappingTree.ClassMapping classDef : mappings.getClasses()) {
+ String ownerSrg = classDef.getName("srg");
+ String ownerIntermediary = classDef.getName("intermediary");
+
+ for (MappingTree.FieldMapping fieldDef : classDef.getFields()) {
+ String fieldSrg = fieldDef.getName("srg");
+ String descriptorSrg = fieldDef.getDesc("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.getDesc("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..9e1e4a7a
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java
@@ -0,0 +1,115 @@
+/*
+ * 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.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);
+ private File globalCache;
+ private File projectCache;
+
+ 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;
+ }
+
+ public File getGlobalCache() {
+ if (globalCache == null) {
+ globalCache = getMinecraftProvider().dir("forge/" + version.getCombined());
+ globalCache.mkdirs();
+ }
+
+ return globalCache;
+ }
+
+ public File getProjectCache() {
+ if (projectCache == null) {
+ projectCache = new File(getDirectories().getRootProjectPersistentCache(), getMinecraftProvider().minecraftVersion() + "/forge/" + getExtension().getForgeProvider().getVersion().getCombined() + "/project-" + getProject().getPath().replace(':', '@'));
+ projectCache.mkdirs();
+ }
+
+ return projectCache;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.FORGE;
+ }
+
+ public static final class ForgeVersion {
+ private final String combined;
+ private final String minecraftVersion;
+ private final String forgeVersion;
+
+ public ForgeVersion(String combined) {
+ this.combined = 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 getCombined() {
+ return 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..b51299fb
--- /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(getExtension().getForgeProvider().getGlobalCache(), "forge-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..358fd110
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java
@@ -0,0 +1,317 @@
+/*
+ * 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.IOException;
+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.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+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 org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.transform.InputArtifact;
+import org.gradle.api.artifacts.transform.TransformAction;
+import org.gradle.api.artifacts.transform.TransformOutputs;
+import org.gradle.api.artifacts.transform.TransformParameters;
+import org.gradle.api.artifacts.type.ArtifactTypeDefinition;
+import org.gradle.api.attributes.Attribute;
+import org.gradle.api.file.FileSystemLocation;
+import org.gradle.api.provider.Provider;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.configuration.ide.RunConfigSettings;
+import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
+import net.fabricmc.loom.api.ForgeLocalMod;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.DependencyDownloader;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.PropertyUtil;
+
+public class ForgeUserdevProvider extends DependencyProvider {
+ private File userdevJar;
+ private JsonObject json;
+ private Consumer<Runnable> postPopulationScheduler;
+
+ public ForgeUserdevProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ this.postPopulationScheduler = postPopulationScheduler;
+ Attribute<Boolean> transformed = Attribute.of("architectury-loom-forge-dependencies-transformed", Boolean.class);
+
+ getProject().getDependencies().registerTransform(RemoveNameProvider.class, spec -> {
+ spec.getFrom().attribute(transformed, false);
+ spec.getTo().attribute(transformed, true);
+ });
+
+ for (ArtifactTypeDefinition type : getProject().getDependencies().getArtifactTypes()) {
+ type.getAttributes().attribute(transformed, false);
+ }
+
+ userdevJar = new File(getExtension().getForgeProvider().getGlobalCache(), "forge-userdev.jar");
+ Path configJson = getExtension().getForgeProvider().getGlobalCache().toPath().resolve("forge-config.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);
+ }
+ }
+
+ 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()) {
+ Dependency dep = null;
+
+ if (lib.getAsString().startsWith("org.spongepowered:mixin:")) {
+ if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) {
+ if (lib.getAsString().contains("0.8.2")) {
+ dep = addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES);
+ } else {
+ dep = addDependency("dev.architectury:mixin-patched" + lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+", Constants.Configurations.FORGE_DEPENDENCIES);
+ }
+ }
+ }
+
+ if (dep == null) {
+ dep = addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES);
+ }
+
+ if (lib.getAsString().split(":").length < 4) {
+ ((ModuleDependency) dep).attributes(attributes -> {
+ attributes.attribute(transformed, true);
+ });
+ }
+ }
+
+ // TODO: Should I copy the patches from here as well?
+ // That'd require me to run the "MCP environment" fully up to merging.
+ for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject("runs").entrySet()) {
+ LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(entry.getKey());
+ RunConfigSettings settings = getExtension().getRunConfigs().findByName(entry.getKey());
+ JsonObject value = entry.getValue().getAsJsonObject();
+
+ if (launchSettings != null) {
+ launchSettings.evaluateLater(() -> {
+ if (value.has("args")) {
+ launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false)
+ .map(JsonElement::getAsString)
+ .map(this::processTemplates)
+ .collect(Collectors.toList()));
+ }
+
+ if (value.has("props")) {
+ for (Map.Entry<String, JsonElement> props : value.getAsJsonObject("props").entrySet()) {
+ String string = processTemplates(props.getValue().getAsString());
+
+ launchSettings.property(props.getKey(), string);
+ }
+ }
+ });
+ }
+
+ if (settings != null) {
+ settings.evaluateLater(() -> {
+ settings.defaultMainClass(value.getAsJsonPrimitive("main").getAsString());
+
+ if (value.has("jvmArgs")) {
+ settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false)
+ .map(JsonElement::getAsString)
+ .map(this::processTemplates)
+ .collect(Collectors.toList()));
+ }
+
+ if (value.has("env")) {
+ for (Map.Entry<String, JsonElement> env : value.getAsJsonObject("env").entrySet()) {
+ String string = processTemplates(env.getValue().getAsString());
+
+ settings.envVariables.put(env.getKey(), string);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ public abstract static class RemoveNameProvider implements TransformAction<TransformParameters.None> {
+ @InputArtifact
+ public abstract Provider<FileSystemLocation> getInput();
+
+ @Override
+ public void transform(TransformOutputs outputs) {
+ try {
+ File input = getInput().get().getAsFile();
+ //architectury-loom-forge-dependencies-transformed
+ File output = outputs.file(input.getName() + "-alfd-transformed.jar");
+ Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(output, false)) {
+ Path path = fs.get().getPath("META-INF/services/cpw.mods.modlauncher.api.INameMappingService");
+ Files.deleteIfExists(path);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public String processTemplates(String string) {
+ if (string.startsWith("{")) {
+ String key = string.substring(1, string.length() - 1);
+
+ // TODO: Look into ways to not hardcode
+ if (key.equals("runtime_classpath")) {
+ string = runtimeClasspath().stream()
+ .map(File::getAbsolutePath)
+ .collect(Collectors.joining(File.pathSeparator));
+ } else if (key.equals("minecraft_classpath")) {
+ string = minecraftClasspath().stream()
+ .map(File::getAbsolutePath)
+ .collect(Collectors.joining(File.pathSeparator));
+ } else if (key.equals("runtime_classpath_file")) {
+ Path path = getDirectories().getProjectPersistentCache().toPath().resolve("forge_runtime_classpath.txt");
+
+ postPopulationScheduler.accept(() -> {
+ try {
+ Files.writeString(path, runtimeClasspath().stream()
+ .map(File::getAbsolutePath)
+ .collect(Collectors.joining("\n")),
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ string = path.toAbsolutePath().toString();
+ } else if (key.equals("minecraft_classpath_file")) {
+ Path path = getDirectories().getProjectPersistentCache().toPath().resolve("forge_minecraft_classpath.txt");
+
+ postPopulationScheduler.accept(() -> {
+ try {
+ Files.writeString(path, minecraftClasspath().stream()
+ .map(File::getAbsolutePath)
+ .collect(Collectors.joining("\n")),
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ string = path.toAbsolutePath().toString();
+ } else if (key.equals("asset_index")) {
+ string = getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion());
+ } else if (key.equals("assets_root")) {
+ string = new File(getDirectories().getUserCache(), "assets").getAbsolutePath();
+ } else if (key.equals("natives")) {
+ string = getMinecraftProvider().nativesDir().getAbsolutePath();
+ } else if (key.equals("source_roots")) {
+ List<String> modClasses = new ArrayList<>();
+
+ for (ForgeLocalMod localMod : getExtension().getForge().getLocalMods()) {
+ String sourceSetName = localMod.getName();
+
+ localMod.getSourceSets().flatMap(sourceSet -> Stream.concat(
+ Stream.of(sourceSet.getOutput().getResourcesDir()),
+ sourceSet.getOutput().getClassesDirs().getFiles().stream())
+ ).map(File::getAbsolutePath).distinct().map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses));
+ }
+
+ string = String.join(File.pathSeparator, modClasses);
+ } else if (key.equals("mcp_mappings")) {
+ string = "loom.stub";
+ } else if (json.has(key)) {
+ JsonElement element = json.get(key);
+
+ if (element.isJsonArray()) {
+ string = StreamSupport.stream(element.getAsJsonArray().spliterator(), false)
+ .map(JsonElement::getAsString)
+ .flatMap(str -> {
+ if (str.contains(":")) {
+ return DependencyDownloader.download(getProject(), str, false, false).getFiles().stream()
+ .map(File::getAbsolutePath)
+ .filter(dep -> !dep.contains("bootstraplauncher")); // TODO: Hack
+ }
+
+ return Stream.of(str);
+ })
+ .collect(Collectors.joining(File.pathSeparator));
+ } else {
+ string = element.toString();
+ }
+ } else {
+ getProject().getLogger().warn("Unrecognized template! " + string);
+ }
+ }
+
+ return string;
+ }
+
+ private Set<File> runtimeClasspath() {
+ // Should we actually include the runtime classpath here? Forge doesn't seem to be using this property anyways
+ return minecraftClasspath();
+ }
+
+ private Set<File> minecraftClasspath() {
+ return DependencyDownloader.resolveFiles(getProject().getConfigurations().getByName(Constants.Configurations.FORGE_RUNTIME_LIBRARY), true);
+ }
+
+ 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..7404cc0a
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java
@@ -0,0 +1,232 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import org.gradle.api.Project;
+import org.gradle.api.file.FileCollection;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.DependencyDownloader;
+import net.fabricmc.loom.util.ZipUtils;
+
+public class McpConfigProvider extends DependencyProvider {
+ private Path mcp;
+ private Path configJson;
+ private Path mappings;
+ private Boolean official;
+ private String mappingsPath;
+ private RemapAction remapAction;
+
+ public McpConfigProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ init(dependency.getDependency().getVersion());
+
+ Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath();
+
+ if (!Files.exists(mcp) || !Files.exists(configJson) || isRefreshDeps()) {
+ Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING);
+ Files.write(configJson, ZipUtils.unpack(mcp, "config.json"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ }
+
+ JsonObject json;
+
+ try (Reader reader = Files.newBufferedReader(configJson)) {
+ json = new Gson().fromJson(reader, JsonObject.class);
+ }
+
+ official = json.has("official") && json.getAsJsonPrimitive("official").getAsBoolean();
+ mappingsPath = json.get("data").getAsJsonObject().get("mappings").getAsString();
+
+ if (json.has("functions")) {
+ JsonObject functions = json.getAsJsonObject("functions");
+
+ if (functions.has("rename")) {
+ remapAction = new ConfigDefinedRemapAction(getProject(), functions.getAsJsonObject("rename"));
+ }
+ }
+
+ if (remapAction == null) {
+ throw new RuntimeException("Could not find remap action, this is probably a version Architectury Loom does not support!");
+ }
+ }
+
+ public RemapAction getRemapAction() {
+ return remapAction;
+ }
+
+ private void init(String version) throws IOException {
+ Path dir = getMinecraftProvider().dir("mcp/" + version).toPath();
+ mcp = dir.resolve("mcp.zip");
+ configJson = dir.resolve("mcp-config.json");
+ mappings = dir.resolve("mcp-config-mappings.txt");
+
+ if (isRefreshDeps()) {
+ Files.deleteIfExists(mappings);
+ }
+ }
+
+ public Path getMappings() {
+ if (Files.notExists(mappings)) {
+ try {
+ Files.write(mappings, ZipUtils.unpack(getMcp(), getMappingsPath()), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to find mappings '" + getMappingsPath() + "' in " + getMcp() + "!");
+ }
+ }
+
+ return mappings;
+ }
+
+ public Path getMcp() {
+ return mcp;
+ }
+
+ public boolean isOfficial() {
+ return official;
+ }
+
+ public String getMappingsPath() {
+ return mappingsPath;
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.MCP_CONFIG;
+ }
+
+ public interface RemapAction {
+ FileCollection getClasspath();
+
+ String getMainClass();
+
+ List<String> getArgs(Path input, Path output, Path mappings, FileCollection libraries);
+ }
+
+ public static class ConfigDefinedRemapAction implements RemapAction {
+ private final Project project;
+ private final String name;
+ private final File mainClasspath;
+ private final FileCollection classpath;
+ private final List<String> args;
+ private boolean hasLibraries;
+
+ public ConfigDefinedRemapAction(Project project, JsonObject json) {
+ this.project = project;
+ this.name = json.get("version").getAsString();
+ this.mainClasspath = DependencyDownloader.download(project, this.name, false, true)
+ .getSingleFile();
+ this.classpath = DependencyDownloader.download(project, this.name, true, true);
+ this.args = StreamSupport.stream(json.getAsJsonArray("args").spliterator(), false)
+ .map(JsonElement::getAsString)
+ .collect(Collectors.toList());
+ for (int i = 1; i < this.args.size(); i++) {
+ if (this.args.get(i).equals("{libraries}")) {
+ this.args.remove(i);
+ this.args.remove(i - 1);
+ this.hasLibraries = true;
+ break;
+ }
+ }
+ }
+
+ @Override
+ public FileCollection getClasspath() {
+ return classpath;
+ }
+
+ @Override
+ public String getMainClass() {
+ try {
+ byte[] manifestBytes = ZipUtils.unpackNullable(mainClasspath.toPath(), "META-INF/MANIFEST.MF");
+
+ if (manifestBytes == null) {
+ throw new RuntimeException("Could not find MANIFEST.MF in " + mainClasspath + "!");
+ }
+
+ Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
+ Attributes attributes = manifest.getMainAttributes();
+ String value = attributes.getValue(Attributes.Name.MAIN_CLASS);
+
+ if (value == null) {
+ throw new RuntimeException("Could not find main class in " + mainClasspath + "!");
+ } else {
+ return value;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public List<String> getArgs(Path input, Path output, Path mappings, FileCollection libraries) {
+ List<String> args = this.args.stream()
+ .map(str -> {
+ return switch (str) {
+ case "{input}" -> input.toAbsolutePath().toString();
+ case "{output}" -> output.toAbsolutePath().toString();
+ case "{mappings}" -> mappings.toAbsolutePath().toString();
+ default -> str;
+ };
+ })
+ .collect(Collectors.toList());
+
+ if (hasLibraries) {
+ for (File file : libraries) {
+ args.add("-e=" + file.getAbsolutePath());
+ }
+ }
+
+ return args;
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+ }
+}
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..fadf7d33
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java
@@ -0,0 +1,786 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+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.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.regex.Pattern;
+import java.util.stream.Stream;
+
+import com.google.common.base.Preconditions;
+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 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.output.NullOutputStream;
+import org.gradle.api.Project;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.configuration.ShowStacktrace;
+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.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.DependencyDownloader;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.MappingsProviderVerbose;
+import net.fabricmc.loom.util.ThreadingUtils;
+import net.fabricmc.loom.util.TinyRemapperHelper;
+import net.fabricmc.loom.util.ZipUtils;
+import net.fabricmc.loom.util.function.FsPathConsumer;
+import net.fabricmc.loom.util.srg.InnerClassRemapper;
+import net.fabricmc.loom.util.srg.SpecialSourceExecutor;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+public class MinecraftPatchedProvider extends DependencyProvider {
+ private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version";
+ private static final String CURRENT_LOOM_PATCH_VERSION = "5";
+ 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 minecraftClientExtra;
+
+ private File projectAtHash;
+ private Set<File> projectAts = new HashSet<>();
+ private boolean atDirty = false;
+ private boolean filesDirty = false;
+ private Path mcpConfigMappings;
+ private Path[] mergedMojangTsrg2Files;
+
+ public MinecraftPatchedProvider(Project project) {
+ super(project);
+ }
+
+ public void initFiles() throws IOException {
+ filesDirty = false;
+ projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256");
+ ConfigurableFileCollection accessTransformers = getExtension().getForge().getAccessTransformers();
+ accessTransformers.finalizeValue();
+ projectAts = accessTransformers.getFiles();
+
+ if (projectAts.isEmpty()) {
+ SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main");
+
+ for (File srcDir : main.getResources().getSrcDirs()) {
+ File projectAt = new File(srcDir, Constants.Forge.ACCESS_TRANSFORMER_PATH);
+
+ 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-" + getExtension().getForgeProvider().getVersion().getCombined() + "-";
+
+ if (getExtension().isForgeAndOfficial()) {
+ minecraftProvider.setJarPrefix(patchId);
+ }
+
+ File globalCache = getExtension().getForgeProvider().getGlobalCache();
+ File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache;
+ projectDir.mkdirs();
+
+ minecraftClientSrgJar = new File(globalCache, "minecraft-client-srg.jar");
+ minecraftServerSrgJar = new File(globalCache, "minecraft-server-srg.jar");
+ minecraftClientPatchedSrgJar = new File(globalCache, "client-srg-patched.jar");
+ minecraftServerPatchedSrgJar = new File(globalCache, "server-srg-patched.jar");
+ minecraftMergedPatchedSrgJar = new File(globalCache, "merged-srg-patched.jar");
+ forgeMergedJar = getExtension().isForgeAndOfficial() ? null : new File(globalCache, "forge-official.jar");
+ minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar");
+ minecraftMergedPatchedJar = new File(projectDir, "merged-patched.jar");
+ minecraftClientExtra = new File(globalCache, "forge-client-extra.jar");
+
+ if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate<File>) File::exists).negate())
+ || !isPatchedJarUpToDate(minecraftMergedPatchedJar)) {
+ cleanAllCache();
+ } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate<File>) File::exists).negate())) {
+ 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() {
+ File[] files = {
+ minecraftClientSrgJar,
+ minecraftServerSrgJar,
+ minecraftClientPatchedSrgJar,
+ minecraftServerPatchedSrgJar,
+ minecraftMergedPatchedSrgJar,
+ minecraftClientExtra,
+ };
+
+ if (forgeMergedJar != null) {
+ Arrays.copyOf(files, files.length + 1);
+ files[files.length - 1] = forgeMergedJar;
+ }
+
+ return files;
+ }
+
+ 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 != null && !forgeMergedJar.exists()) {
+ this.dirty = true;
+ }
+
+ if (dirty) {
+ remapPatchedJar(getProject().getLogger());
+
+ if (getExtension().isForgeAndOfficial()) {
+ fillClientExtraJar();
+ }
+ }
+
+ this.filesDirty = dirty;
+ this.dirty = false;
+
+ if (getExtension().isForgeAndOfficial()) {
+ addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA);
+ }
+ }
+
+ private void fillClientExtraJar() throws IOException {
+ Files.deleteIfExists(minecraftClientExtra.toPath());
+ FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close();
+
+ copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra);
+ }
+
+ private TinyRemapper buildRemapper(Path input) throws IOException {
+ Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject());
+ MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg();
+
+ TinyRemapper remapper = TinyRemapper.newRemapper()
+ .logger(getProject().getLogger()::lifecycle)
+ .logUnknownInvokeDynamic(false)
+ .withMappings(TinyRemapperHelper.create(mappingsWithSrg, "srg", "official", true))
+ .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, "srg", "official"))
+ .renameInvalidLocals(true)
+ .rebuildSourceFilenames(true)
+ .fixPackageAccess(true)
+ .build();
+
+ if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) {
+ MappingsProviderVerbose.saveFile(remapper);
+ }
+
+ 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 {
+ MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider();
+ produceSrgJar(getExtension().isForgeAndOfficial(), minecraftProvider.minecraftClientJar.toPath(), minecraftProvider.getMinecraftServerJar().toPath());
+ }
+
+ private void produceSrgJar(boolean official, Path clientJar, Path serverJar) throws IOException {
+ Path tmpSrg = getToSrgMappings();
+ Set<File> mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve();
+
+ ThreadingUtils.run(() -> {
+ Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg), minecraftClientSrgJar.toPath());
+ }, () -> {
+ Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg), minecraftServerSrgJar.toPath());
+ });
+ }
+
+ private Path getToSrgMappings() throws IOException {
+ if (getExtension().getSrgProvider().isTsrgV2()) {
+ return getExtension().getSrgProvider().getMergedMojangRaw();
+ } else {
+ return getExtension().getMcpConfigProvider().getMappings();
+ }
+ }
+
+ 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 void deleteParameterNames(File jarFile) throws Exception {
+ getProject().getLogger().info(":deleting parameter names 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();
+ Pattern vignetteParameters = Pattern.compile("p_[0-9a-zA-Z]+_(?:[0-9a-zA-Z]+_)?");
+
+ 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);
+ ClassWriter writer = new ClassWriter(0);
+
+ reader.accept(new ClassVisitor(Opcodes.ASM9, writer) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) {
+ @Override
+ public void visitParameter(String name, int access) {
+ if (vignetteParameters.matcher(name).matches()) {
+ super.visitParameter(null, access);
+ } else {
+ super.visitParameter(name, access);
+ }
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
+ if (!vignetteParameters.matcher(name).matches()) {
+ super.visitLocalVariable(name, descriptor, signature, start, end, index);
+ }
+ }
+ };
+ }
+ }, 0);
+
+ byte[] out = writer.toByteArray();
+
+ if (!Arrays.equals(bytes, out)) {
+ Files.delete(file);
+ Files.write(file, out);
+ }
+ });
+ }
+
+ completer.complete();
+ }
+
+ getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch);
+ }
+
+ private File getForgeJar() {
+ return getExtension().getForgeUniversalProvider().getForge();
+ }
+
+ private File getForgeUserdevJar() {
+ return getExtension().getForgeUserdevProvider().getUserdevJar();
+ }
+
+ private boolean isPatchedJarUpToDate(File jar) throws IOException {
+ if (!jar.exists()) return false;
+
+ byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF");
+
+ if (manifestBytes == null) {
+ return false;
+ }
+
+ Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
+ Attributes attributes = manifest.getMainAttributes();
+ String value = attributes.getValue(LOOM_PATCH_VERSION_KEY);
+
+ if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) {
+ return true;
+ } else {
+ getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value);
+ return false;
+ }
+ }
+
+ private void accessTransformForge(Logger logger) throws Exception {
+ MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider();
+ List<File> toDelete = new ArrayList<>();
+ String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (minecraftProvider.isNewerThan21w39a() ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS);
+ FileCollection classpath = DependencyDownloader.download(getProject(), atDependency);
+ Stopwatch stopwatch = Stopwatch.createStarted();
+
+ 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)) {
+ byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH);
+
+ if (atBytes != null) {
+ File tmpFile = File.createTempFile("at-conf", ".cfg");
+ toDelete.add(tmpFile);
+ Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ args.add("--atFile");
+ args.add(tmpFile.getAbsolutePath());
+ }
+ }
+
+ if (usesProjectCache()) {
+ for (File projectAt : projectAts) {
+ args.add("--atFile");
+ args.add(projectAt.getAbsolutePath());
+ }
+ }
+
+ getProject().javaexec(spec -> {
+ spec.getMainClass().set("net.minecraftforge.accesstransformer.TransformerProcessor");
+ spec.setArgs(args);
+ spec.setClasspath(classpath);
+
+ // if running with INFO or DEBUG logging
+ if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS
+ || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) {
+ spec.setStandardOutput(System.out);
+ spec.setErrorOutput(System.err);
+ } else {
+ spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM);
+ spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM);
+ }
+ }).rethrowFailure().assertNormalExitValue();
+
+ for (File file : toDelete) {
+ file.delete();
+ }
+
+ logger.lifecycle(":access transformed minecraft in " + stopwatch.stop());
+ }
+
+ 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(Logger logger) throws Exception {
+ getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)");
+ Path mcInput = minecraftMergedPatchedSrgAtJar.toPath();
+ Path mcOutput = minecraftMergedPatchedJar.toPath();
+ Path forgeJar = getForgeJar().toPath();
+ Path forgeUserdevJar = getForgeUserdevJar().toPath();
+ Path forgeOutput = null;
+ Files.deleteIfExists(mcOutput);
+ boolean splitJars = forgeMergedJar != null;
+
+ if (splitJars) {
+ forgeOutput = forgeMergedJar.toPath();
+ Files.deleteIfExists(forgeOutput);
+ }
+
+ TinyRemapper remapper = buildRemapper(mcInput);
+
+ try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build();
+ Closeable outputConsumerForge = !splitJars ? () -> {
+ } : new OutputConsumerPath.Builder(forgeOutput).build()) {
+ outputConsumer.addNonClassFiles(mcInput);
+
+ InputTag mcTag = remapper.createInputTag();
+ InputTag forgeTag = remapper.createInputTag();
+ List<CompletableFuture<?>> futures = new ArrayList<>();
+ futures.add(remapper.readInputsAsync(mcTag, mcInput));
+ futures.add(remapper.readInputsAsync(forgeTag, forgeJar, forgeUserdevJar));
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+ remapper.apply(outputConsumer, mcTag);
+ remapper.apply(splitJars ? (OutputConsumerPath) outputConsumerForge : outputConsumer, forgeTag);
+ } finally {
+ remapper.finish();
+ }
+
+ copyNonClassFiles(forgeJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedJar);
+ copyUserdevFiles(forgeUserdevJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedJar);
+ applyLoomPatchVersion(mcOutput);
+ }
+
+ 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));
+ deleteParameterNames(environment.patchedSrgJar.apply(this));
+
+ if (getExtension().isForgeAndNotOfficial()) {
+ 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
+ if (getExtension().isForgeAndNotOfficial()) {
+ // Copy resources
+ MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider();
+ copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedSrgJar);
+ copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedSrgJar);
+ }
+ }
+
+ private void walkFileSystems(File source, File target, Predicate<Path> filter, Function<FileSystem, Iterable<Path>> toWalk, FsPathConsumer action)
+ throws IOException {
+ try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false);
+ FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) {
+ for (Path sourceDir : toWalk.apply(sourceFs.get())) {
+ Path dir = sourceDir.toAbsolutePath();
+ if (!Files.exists(dir)) continue;
+ 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 = getExtension().isForgeAndOfficial() ? file -> {
+ String s = file.toString();
+ return !s.endsWith(".class");
+ } : file -> {
+ String s = file.toString();
+ return !s.endsWith(".class") || (s.startsWith("META-INF") && !s.startsWith("META-INF/services"));
+ };
+
+ 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.Delegate 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..d49ae8b4
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java
@@ -0,0 +1,88 @@
+/*
+ * 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 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) {
+ projectCacheFolder = getMinecraftProvider().dir("forge/" + forgeVersion).toPath();
+ 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..32a12dc6
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java
@@ -0,0 +1,273 @@
+/*
+ * 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.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.StringReader;
+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.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.output.NullOutputStream;
+import org.gradle.api.Project;
+import org.gradle.api.logging.LogLevel;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.LoomGradlePlugin;
+import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
+import net.fabricmc.loom.configuration.DependencyProvider;
+import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext;
+import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingLayer;
+import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsSpec;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.srg.Tsrg2Utils;
+import net.fabricmc.loom.util.srg.Tsrg2Writer;
+import net.fabricmc.mappingio.MappingReader;
+import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.adapter.ForwardingMappingVisitor;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+public class SrgProvider extends DependencyProvider {
+ private Path srg;
+ private Boolean isTsrgV2;
+ private Path mergedMojangRaw;
+ private Path mergedMojang;
+ private Path mergedMojangTrimmed;
+ private static Path mojmapTsrg;
+ private static Path mojmapTsrg2;
+
+ public SrgProvider(Project project) {
+ super(project);
+ }
+
+ @Override
+ public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
+ init(dependency.getDependency().getVersion());
+
+ if (!Files.exists(srg) || isRefreshDeps()) {
+ Path srgZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve srg")).toPath();
+
+ try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) {
+ Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ try (BufferedReader reader = Files.newBufferedReader(srg)) {
+ isTsrgV2 = reader.readLine().startsWith("tsrg2");
+ }
+
+ if (isTsrgV2) {
+ if (!Files.exists(mergedMojangRaw) || !Files.exists(mergedMojang) || !Files.exists(mergedMojangTrimmed) || isRefreshDeps()) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ getProject().getLogger().lifecycle(":merging mappings (InstallerTools, srg + mojmap)");
+ PrintStream out = System.out;
+ PrintStream err = System.err;
+
+ if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) >= 0) {
+ System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
+ System.setErr(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
+ }
+
+ Files.deleteIfExists(mergedMojangRaw);
+ Files.deleteIfExists(mergedMojang);
+ net.minecraftforge.installertools.ConsoleTool.main(new String[] {
+ "--task",
+ "MERGE_MAPPING",
+ "--left",
+ getSrg().toAbsolutePath().toString(),
+ "--right",
+ getMojmapTsrg2(getProject(), getExtension()).toAbsolutePath().toString(),
+ "--classes",
+ "--output",
+ mergedMojangRaw.toAbsolutePath().toString()
+ });
+
+ MemoryMappingTree tree = new MemoryMappingTree();
+ MappingReader.read(new StringReader(FileUtils.readFileToString(mergedMojangRaw.toFile(), StandardCharsets.UTF_8)), new FieldDescWrappingVisitor(tree));
+ Files.writeString(mergedMojang, Tsrg2Writer.serialize(tree), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+
+ for (MappingTree.ClassMapping classDef : tree.getClasses()) {
+ for (MappingTree.MethodMapping methodDef : classDef.getMethods()) {
+ methodDef.getArgs().clear();
+ }
+ }
+
+ Files.writeString(mergedMojangTrimmed, Tsrg2Writer.serialize(tree), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+
+ if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) >= 0) {
+ System.setOut(out);
+ System.setErr(err);
+ }
+
+ getProject().getLogger().lifecycle(":merged mappings (InstallerTools, srg + mojmap) in " + stopwatch.stop());
+ }
+ }
+ }
+
+ // Read mojmap and apply field descs to the tsrg2
+ private class FieldDescWrappingVisitor extends ForwardingMappingVisitor {
+ private final Map<FieldKey, String> fieldDescMap = new HashMap<>();
+ private String lastClass;
+
+ protected FieldDescWrappingVisitor(MappingVisitor next) throws IOException {
+ super(next);
+ MemoryMappingTree mojmap = new MemoryMappingTree();
+ MappingReader.read(getMojmapTsrg2(getProject(), getExtension()), mojmap);
+
+ for (MappingTree.ClassMapping classMapping : mojmap.getClasses()) {
+ for (MappingTree.FieldMapping fieldMapping : classMapping.getFields()) {
+ fieldDescMap.put(new FieldKey(classMapping.getSrcName(), fieldMapping.getSrcName()), fieldMapping.getSrcDesc());
+ }
+ }
+ }
+
+ @Override
+ public boolean visitClass(String srcName) throws IOException {
+ if (super.visitClass(srcName)) {
+ this.lastClass = srcName;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean visitField(String srcName, String srcDesc) throws IOException {
+ if (srcDesc == null) {
+ srcDesc = fieldDescMap.get(new FieldKey(lastClass, srcName));
+ }
+
+ return super.visitField(srcName, srcDesc);
+ }
+
+ private record FieldKey(String owner, String name) {
+ }
+ }
+
+ private void init(String version) {
+ File dir = getMinecraftProvider().dir("srg/" + version);
+ srg = new File(dir, "srg.tsrg").toPath();
+ mergedMojangRaw = new File(dir, "srg-mojmap-merged-raw.tsrg").toPath();
+ mergedMojang = new File(dir, "srg-mojmap-merged.tsrg").toPath();
+ mergedMojangTrimmed = new File(dir, "srg-mojmap-merged-trimmed.tsrg").toPath();
+ }
+
+ public Path getSrg() {
+ return srg;
+ }
+
+ public Path getMergedMojangRaw() {
+ if (!isTsrgV2()) throw new IllegalStateException("May not access merged mojmap srg if not on modern Minecraft!");
+
+ return mergedMojangRaw;
+ }
+
+ public Path getMergedMojang() {
+ if (!isTsrgV2()) throw new IllegalStateException("May not access merged mojmap srg if not on modern Minecraft!");
+
+ return mergedMojang;
+ }
+
+ public Path getMergedMojangTrimmed() {
+ if (!isTsrgV2()) throw new IllegalStateException("May not access merged mojmap srg if not on modern Minecraft!");
+
+ return mergedMojangTrimmed;
+ }
+
+ public boolean isTsrgV2() {
+ return isTsrgV2;
+ }
+
+ public static Path getMojmapTsrg(Project project, LoomGradleExtension extension) throws IOException {
+ if (mojmapTsrg != null) return mojmapTsrg;
+
+ mojmapTsrg = extension.getMinecraftProvider().dir("forge").toPath().resolve("mojmap.tsrg");
+
+ if (Files.notExists(mojmapTsrg) || LoomGradlePlugin.refreshDeps) {
+ try (BufferedWriter writer = Files.newBufferedWriter(mojmapTsrg, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ Tsrg2Utils.writeTsrg(visitor -> visitMojmap(visitor, project),
+ MappingsNamespace.NAMED.toString(), false, writer);
+ }
+ }
+
+ return mojmapTsrg;
+ }
+
+ public static Path getMojmapTsrg2(Project project, LoomGradleExtension extension) throws IOException {
+ if (mojmapTsrg2 != null) return mojmapTsrg2;
+
+ mojmapTsrg2 = extension.getMinecraftProvider().dir("forge").toPath().resolve("mojmap.tsrg2");
+
+ if (Files.notExists(mojmapTsrg2) || LoomGradlePlugin.refreshDeps) {
+ try (BufferedWriter writer = Files.newBufferedWriter(mojmapTsrg2, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ MemoryMappingTree tree = new MemoryMappingTree();
+ visitMojmap(tree, project);
+ writer.write(Tsrg2Writer.serialize(tree));
+ }
+ }
+
+ return mojmapTsrg2;
+ }
+
+ private static void visitMojmap(MappingVisitor visitor, Project project) {
+ GradleMappingContext context = new GradleMappingContext(project, "tmp-mojmap");
+
+ try {
+ FileUtils.deleteDirectory(context.workingDirectory("/").toFile());
+ MojangMappingLayer layer = new MojangMappingsSpec(() -> true, true).createLayer(context);
+ layer.visit(visitor);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ } finally {
+ try {
+ FileUtils.deleteDirectory(context.workingDirectory("/").toFile());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public String getTargetConfig() {
+ return Constants.Configurations.SRG;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java
index b3746258..a2ffaa3c 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingSpecBuilderImpl.java
@@ -29,12 +29,15 @@ import java.util.LinkedList;
import java.util.List;
import org.gradle.api.Action;
+import org.jetbrains.annotations.Nullable;
import net.fabricmc.loom.api.mappings.layered.spec.FileSpec;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec;
import net.fabricmc.loom.api.mappings.layered.spec.MojangMappingsSpecBuilder;
import net.fabricmc.loom.api.mappings.layered.spec.ParchmentMappingsSpecBuilder;
+import net.fabricmc.loom.api.LoomGradleExtensionAPI;
+import net.fabricmc.loom.configuration.providers.mappings.crane.CraneMappingsSpec;
import net.fabricmc.loom.configuration.providers.mappings.extras.signatures.SignatureFixesSpec;
import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingsSpec;
import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsSpecBuilderImpl;
@@ -42,6 +45,12 @@ import net.fabricmc.loom.configuration.providers.mappings.parchment.ParchmentMap
public class LayeredMappingSpecBuilderImpl implements LayeredMappingSpecBuilder {
private final List<MappingsSpec<?>> layers = new LinkedList<>();
+ @Nullable
+ private final LoomGradleExtensionAPI extension;
+
+ public LayeredMappingSpecBuilderImpl(@Nullable LoomGradleExtensionAPI extension) {
+ this.extension = extension;
+ }
@Override
public LayeredMappingSpecBuilder addLayer(MappingsSpec<?> mappingSpec) {
@@ -53,7 +62,8 @@ public class LayeredMappingSpecBuilderImpl implements LayeredMappingSpecBuilder
public LayeredMappingSpecBuilder officialMojangMappings(Action<MojangMappingsSpecBuilder> action) {
MojangMappingsSpecBuilderImpl builder = MojangMappingsSpecBuilderImpl.builder();
action.execute(builder);
- return addLayer(builder.build());
+ layers.add(builder.build(() -> extension != null && extension.isSilentMojangMappingsLicenseEnabled()));
+ return this;
}
@Override
@@ -64,6 +74,12 @@ public class LayeredMappingSpecBuilderImpl implements LayeredMappingSpecBuilder
}
@Override
+ public LayeredMappingSpecBuilder crane(Object object) {
+ layers.add(new CraneMappingsSpec(FileSpec.create(object)));
+ return this;
+ }
+
+ @Override
public LayeredMappingSpecBuilder signatureFix(Object object) {
return addLayer(new SignatureFixesSpec(FileSpec.create(object)));
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java
index dcfae66b..6a47ed71 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsDependency.java
@@ -37,10 +37,21 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.ModuleIdentifier;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.MutableVersionConstraint;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.SelfResolvingDependency;
+import org.gradle.api.artifacts.VersionConstraint;
+import org.gradle.api.internal.artifacts.DefaultModuleIdentifier;
+import org.gradle.api.internal.artifacts.ModuleVersionSelectorStrictSpec;
+import org.gradle.api.internal.artifacts.dependencies.AbstractModuleDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultMutableVersionConstraint;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.TaskDependency;
@@ -54,7 +65,7 @@ import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
-public class LayeredMappingsDependency implements SelfResolvingDependency, FileCollectionDependency {
+public class LayeredMappingsDependency extends AbstractModuleDependency implements SelfResolvingDependency, ExternalModuleDependency, FileCollectionDependency {
private static final String GROUP = "loom";
private static final String MODULE = "mappings";
@@ -64,6 +75,7 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC
private final String version;
public LayeredMappingsDependency(Project project, MappingContext mappingContext, LayeredMappingSpec layeredMappingSpec, String version) {
+ super(null);
this.project = project;
this.mappingContext = mappingContext;
this.layeredMappingSpec = layeredMappingSpec;
@@ -145,6 +157,21 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC
}
@Override
+ public VersionConstraint getVersionConstraint() {
+ return new DefaultMutableVersionConstraint(getVersion());
+ }
+
+ @Override
+ public boolean matchesStrictly(ModuleVersionIdentifier identifier) {
+ return new ModuleVersionSelectorStrictSpec(this).isSatisfiedBy(identifier);
+ }
+
+ @Override
+ public ModuleIdentifier getModule() {
+ return DefaultModuleIdentifier.newId(GROUP, MODULE);
+ }
+
+ @Override
public boolean contentEquals(Dependency dependency) {
if (dependency instanceof LayeredMappingsDependency layeredMappingsDependency) {
return Objects.equals(layeredMappingsDependency.getVersion(), this.getVersion());
@@ -154,11 +181,35 @@ public class LayeredMappingsDependency implements SelfResolvingDependency, FileC
}
@Override
- public Dependency copy() {
+ public boolean isChanging() {
+ return false;
+ }
+
+ @Override
+ public ExternalModuleDependency setChanging(boolean b) {
+ return this;
+ }
+
+ @Override
+ public boolean isForce() {
+ return false;
+ }
+
+ @Override
+ public ExternalDependency setForce(boolean b) {
+ return this;
+ }
+
+ @Override
+ public ExternalModuleDependency copy() {
return new LayeredMappingsDependency(project, mappingContext, layeredMappingSpec, version);
}
@Override
+ public void version(Action<? super MutableVersionConstraint> action) {
+ }
+
+ @Override
public String getReason() {
return null;
}
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 8ed07b90..23bf71c8 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,15 +28,19 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
+import java.io.UncheckedIOException;
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.StandardCopyOption;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
@@ -47,6 +51,7 @@ import com.google.common.net.UrlEscapers;
import com.google.gson.JsonObject;
import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
@@ -59,11 +64,16 @@ import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarP
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.ZipUtils;
+import net.fabricmc.loom.util.srg.MCPReader;
+import net.fabricmc.loom.util.srg.SrgMerger;
+import net.fabricmc.loom.util.srg.SrgNamedWriter;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
@@ -74,9 +84,12 @@ import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.stitch.Command;
import net.fabricmc.stitch.commands.CommandProposeFieldNames;
+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 mappingsIdentifier;
@@ -88,10 +101,14 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
// The mappings we use in practice
public Path tinyMappings;
public Path tinyMappingsJar;
+ public Path tinyMappingsWithSrg;
+ public Path mixinTinyMappingsWithSrg; // FORGE: The mixin mappings have srg names in intermediary.
+ public Path srgToNamedSrg; // FORGE: srg to named in srg file format
private Path unpickDefinitions;
private boolean hasUnpickDefinitions;
private UnpickMetadata unpickMetadata;
private MemoryMappingTree mappingTree;
+ private MemoryMappingTree mappingTreeWithSrg;
private Map<String, String> signatureFixes;
public MappingsProviderImpl(Project project) {
@@ -102,6 +119,10 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
return Objects.requireNonNull(mappingTree, "Cannot get mappings before they have been read");
}
+ public MemoryMappingTree getMappingsWithSrg() throws IOException {
+ return Objects.requireNonNull(mappingTreeWithSrg, "Cannot get mappings before they have been read");
+ }
+
@Override
public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {
MinecraftProviderImpl minecraftProvider = getDependencyManager().getProvider(MinecraftProviderImpl.class);
@@ -118,14 +139,29 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
initFiles();
if (Files.notExists(tinyMappings) || isRefreshDeps()) {
- storeMappings(getProject(), minecraftProvider, mappingsJar.toPath());
+ storeMappings(getProject(), minecraftProvider, mappingsJar.toPath(), postPopulationScheduler);
} else {
try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) {
extractExtras(fileSystem);
}
}
- mappingTree = readMappings();
+ if (getExtension().isForge()) {
+ patchedProvider = new MinecraftPatchedProvider(getProject());
+ patchedProvider.provide(dependency, postPopulationScheduler);
+ }
+
+ mappingTree = readMappings(tinyMappings);
+ manipulateMappings(mappingsJar.toPath());
+
+ if (getExtension().shouldGenerateSrgTiny()) {
+ if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) {
+ // Merge tiny mappings with srg
+ SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true);
+ }
+
+ mappingTreeWithSrg = readMappings(tinyMappingsWithSrg);
+ }
if (Files.notExists(tinyMappingsJar) || isRefreshDeps()) {
Files.deleteIfExists(tinyMappingsJar);
@@ -143,7 +179,24 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
populateUnpickClasspath();
}
- addDependency(tinyMappingsJar.toFile(), Constants.Configurations.MAPPINGS_FINAL);
+ if (getExtension().isForge()) {
+ if (!getExtension().shouldGenerateSrgTiny()) {
+ throw new IllegalStateException("We have to generate srg tiny in a forge environment!");
+ }
+
+ if (Files.notExists(mixinTinyMappingsWithSrg) || isRefreshDeps()) {
+ List<String> lines = new ArrayList<>(Files.readAllLines(tinyMappingsWithSrg));
+ lines.set(0, lines.get(0).replace("intermediary", "yraidemretni").replace("srg", "intermediary"));
+ Files.deleteIfExists(mixinTinyMappingsWithSrg);
+ Files.write(mixinTinyMappingsWithSrg, lines);
+ }
+
+ if (Files.notExists(srgToNamedSrg) || isRefreshDeps()) {
+ SrgNamedWriter.writeTo(getProject().getLogger(), srgToNamedSrg, getMappingsWithSrg(), "srg", "named");
+ }
+ }
+
+ addDependency(getProject().getDependencies().module("loom.resolved:mappings:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()), Constants.Configurations.MAPPINGS_FINAL);
LoomGradleExtension extension = getExtension();
@@ -165,7 +218,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().info("Using project based jar storage");
} else {
@@ -176,6 +233,28 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
mappedProvider.provide(dependency, postPopulationScheduler);
}
+ protected Path getRawSrgFile() throws IOException {
+ LoomGradleExtension extension = getExtension();
+
+ if (extension.getSrgProvider().isTsrgV2()) {
+ return extension.getSrgProvider().getMergedMojangTrimmed();
+ }
+
+ return extension.getSrgProvider().getSrg();
+ }
+
+ public Path getMojmapSrgFileIfPossible() {
+ try {
+ LoomGradleExtension extension = getExtension();
+ return SrgProvider.getMojmapTsrg2(getProject(), extension);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public void manipulateMappings(Path mappingsJar) throws IOException {
+ }
+
private String getMappingsClassifier(DependencyInfo dependency, boolean isV2) {
String[] depStringSplit = dependency.getDepString().split(":");
@@ -200,21 +279,37 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
// We can save reading the zip file + header by checking the file name
- return mappingsJar.getName().endsWith("-v2.jar");
+ return mappingsJar.getName().endsWith("-v2.jar") || mappingsJar.getName().endsWith("-mergedv2.jar");
} else {
return doesJarContainV2Mappings(mappingsJar.toPath());
}
}
- private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar) throws IOException {
+ private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar, Consumer<Runnable> postPopulationScheduler)
+ throws IOException {
project.getLogger().info(":extracting " + yarnJar.getFileName());
+ if (isMCP(yarnJar)) {
+ try {
+ readAndMergeMCP(yarnJar, postPopulationScheduler);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ return;
+ }
+
try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) {
extractMappings(fileSystem, baseTinyMappings);
extractExtras(fileSystem);
}
- if (areMappingsV2(baseTinyMappings)) {
+ if (areMappingsMergedV2(baseTinyMappings)) {
+ // Architectury Loom Patch
+ // If a merged tiny v2 mappings file is provided
+ // Skip merging, should save a lot of time
+ Files.copy(baseTinyMappings, tinyMappings, StandardCopyOption.REPLACE_EXISTING);
+ } else if (areMappingsV2(baseTinyMappings)) {
// These are unmerged v2 mappings
mergeAndSaveMappings(project, baseTinyMappings, tinyMappings);
} else {
@@ -225,15 +320,53 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
}
}
- private MemoryMappingTree readMappings() throws IOException {
+ private static MemoryMappingTree readMappings(Path file) throws IOException {
MemoryMappingTree mappingTree = new MemoryMappingTree();
- MappingReader.read(tinyMappings, mappingTree);
+ MappingReader.read(file, mappingTree);
return mappingTree;
}
+ 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:" + getMinecraftProvider().minecraftVersion());
+ Configuration configuration = getProject().getConfigurations().getByName(provider.getTargetConfig());
+ provider.provide(DependencyInfo.create(getProject(), configuration.getDependencies().iterator().next(), configuration), postPopulationScheduler);
+ }
+
+ Path srgPath = getRawSrgFile();
+ TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar);
+ TinyV2Writer.write(file, tinyMappings);
+ }
+
+ 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 static boolean areMappingsV2(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path)) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2;
+ } catch (NoSuchFileException e) {
+ // TODO: just check the mappings version when Parser supports V1 in readMetadata()
+ return false;
+ }
+ }
+
+ private static boolean areMappingsMergedV2(Path path) throws IOException {
+ try (BufferedReader reader = Files.newBufferedReader(path)) {
+ return MappingReader.detectFormat(reader) == MappingFormat.TINY_2 && MappingReader.getNamespaces(reader).containsAll(Arrays.asList("named", "intermediary", "official"));
+ } catch (NoSuchFileException e) {
+ return false;
}
}
@@ -242,6 +375,8 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2;
}
+ } catch (NoSuchFileException e) {
+ return false;
}
}
@@ -325,22 +460,18 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
Stopwatch stopwatch = Stopwatch.createStarted();
project.getLogger().info(":merging mappings");
- MemoryMappingTree intermediaryTree = new MemoryMappingTree();
- readIntermediaryTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString()));
+ MemoryMappingTree tree = new MemoryMappingTree();
+ MappingSourceNsSwitch sourceNsSwitch = new MappingSourceNsSwitch(tree, MappingsNamespace.OFFICIAL.toString());
+ readIntermediaryTree().accept(sourceNsSwitch);
try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) {
- Tiny2Reader.read(reader, intermediaryTree);
+ Tiny2Reader.read(reader, tree);
}
- MemoryMappingTree officialTree = new MemoryMappingTree();
- MappingNsCompleter nsCompleter = new MappingNsCompleter(officialTree, Map.of(MappingsNamespace.OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString()));
- MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nsCompleter, MappingsNamespace.OFFICIAL.toString());
- intermediaryTree.accept(nsSwitch);
-
- inheritMappedNamesOfEnclosingClasses(officialTree);
+ inheritMappedNamesOfEnclosingClasses(tree);
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) {
- officialTree.accept(writer);
+ tree.accept(writer);
}
project.getLogger().info(":merged mappings in " + stopwatch.stop());
@@ -394,8 +525,8 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
private void suggestFieldNames(MinecraftProviderImpl minecraftProvider, Path oldMappings, Path newMappings) {
Command command = new CommandProposeFieldNames();
runCommand(command, minecraftProvider.getMergedJar().getAbsolutePath(),
- oldMappings.toAbsolutePath().toString(),
- newMappings.toAbsolutePath().toString());
+ oldMappings.toAbsolutePath().toString(),
+ newMappings.toAbsolutePath().toString());
}
private void runCommand(Command command, String... args) {
@@ -412,6 +543,9 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
tinyMappings = mappingsWorkingDir.resolve("mappings.tiny");
tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar");
unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick");
+ tinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-srg.tiny");
+ mixinTinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-mixin-srg.tiny");
+ srgToNamedSrg = mappingsWorkingDir.resolve("mappings-srg-named.srg");
if (isRefreshDeps()) {
cleanFiles();
@@ -459,7 +593,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings
return mappingsWorkingDir;
}
- private String createMappingsIdentifier(String mappingsName, String version, String classifier) {
+ protected String createMappingsIdentifier(String mappingsName, String version, String classifier) {
// mappingsName . mcVersion . version classifier
// Example: net.fabricmc.yarn . 1_16_5 . 1.16.5+build.5 -v2
return mappingsName + "." + getMinecraftProvider().minecraftVersion().replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier;
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..0d1b6c3f
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingLayer.java
@@ -0,0 +1,49 @@
+/*
+ * 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.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import net.fabricmc.loom.api.mappings.layered.MappingLayer;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.format.Tiny2Reader;
+
+public record CraneMappingLayer(Path craneJar) implements MappingLayer {
+ private static final String TINY_FILE_NAME = "crane.tiny";
+
+ @Override
+ public void visit(MappingVisitor visitor) throws IOException {
+ try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(craneJar(), false)) {
+ try (BufferedReader reader = Files.newBufferedReader(fs.get().getPath(TINY_FILE_NAME), StandardCharsets.UTF_8)) {
+ Tiny2Reader.read(reader, visitor);
+ }
+ }
+ }
+}
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..04c98170
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/crane/CraneMappingsSpec.java
@@ -0,0 +1,36 @@
+/*
+ * 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.api.mappings.layered.MappingContext;
+import net.fabricmc.loom.api.mappings.layered.spec.FileSpec;
+import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec;
+
+public record CraneMappingsSpec(FileSpec fileSpec) implements MappingsSpec<CraneMappingLayer> {
+ @Override
+ public CraneMappingLayer createLayer(MappingContext context) {
+ return new CraneMappingLayer(fileSpec().get(context));
+ }
+}
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 a6c06ba9..83b3c5da 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.api.mappings.layered.MappingLayer;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
@@ -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 MappingsNamespace getSourceNamespace() {
return MappingsNamespace.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(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), 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 194b3038..95277f52 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.api.mappings.layered.spec.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 541fd489..5d970e5e 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
@@ -45,20 +45,23 @@ import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.ProGuardReader;
-public record MojangMappingLayer(MinecraftVersionMeta.Download clientDownload,
+public record MojangMappingLayer(String minecraftVersion,
+ MinecraftVersionMeta.Download clientDownload,
MinecraftVersionMeta.Download serverDownload,
Path workingDir, boolean nameSyntheticMembers,
- Logger logger) implements MappingLayer {
+ Logger logger,
+ MojangMappingsSpec.SilenceLicenseOption silenceLicense) implements MappingLayer {
private static final Pattern SYNTHETIC_NAME_PATTERN = Pattern.compile("^(access|this|val\\$this|lambda\\$.*)\\$[0-9]+$");
-
@Override
public void visit(MappingVisitor mappingVisitor) throws IOException {
- Path clientMappings = workingDir().resolve("client.txt");
- Path serverMappings = workingDir().resolve("server.txt");
+ Path clientMappings = workingDir().resolve("%s.client.txt".formatted(minecraftVersion));
+ Path serverMappings = workingDir().resolve("%s.server.txt".formatted(minecraftVersion));
download(clientMappings, serverMappings);
- printMappingsLicense(clientMappings);
+ if (!silenceLicense.isSilent()) {
+ printMappingsLicense(clientMappings);
+ }
// Filter out field names matching the pattern
DstNameFilterMappingVisitor nameFilter = new DstNameFilterMappingVisitor(mappingVisitor, SYNTHETIC_NAME_PATTERN);
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 f77fb57d..43b33c24 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.api.mappings.layered.MappingContext;
import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta;
-public record MojangMappingsSpec(boolean nameSyntheticMembers) implements MappingsSpec<MojangMappingLayer> {
+public record MojangMappingsSpec(SilenceLicenseOption silenceLicense, boolean nameSyntheticMembers) 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, boolean nameSyntheticMembers) {
+ this(new SilenceLicenseOption(supplier), nameSyntheticMembers);
+ }
+
+ public MojangMappingsSpec(boolean nameSyntheticMembers) {
+ this(() -> false, nameSyntheticMembers);
+ }
+
+ @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();
@@ -42,11 +78,13 @@ public record MojangMappingsSpec(boolean nameSyntheticMembers) implements Mappin
}
return new MojangMappingLayer(
+ context.minecraftVersion(),
versionInfo.download(MANIFEST_CLIENT_MAPPINGS),
versionInfo.download(MANIFEST_SERVER_MAPPINGS),
context.workingDirectory("mojang"),
nameSyntheticMembers(),
- context.getLogger()
+ context.getLogger(),
+ silenceLicense()
);
}
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java
index 7937ad1f..fd7c15ad 100644
--- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpecBuilderImpl.java
@@ -48,7 +48,7 @@ public class MojangMappingsSpecBuilderImpl implements MojangMappingsSpecBuilder
return nameSyntheticMembers;
}
- public MojangMappingsSpec build() {
- return new MojangMappingsSpec(nameSyntheticMembers);
+ public MojangMappingsSpec build(MojangMappingsSpec.SilenceLicenseSupplier supplier) {
+ return new MojangMappingsSpec(supplier, nameSyntheticMembers);
}
}
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 264b855c..ec003995 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,14 +28,28 @@ 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.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
+import com.google.common.base.Stopwatch;
+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 dev.architectury.tinyremapper.api.TrClass;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.tuple.Triple;
import org.gradle.api.Project;
+import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.commons.Remapper;
@@ -43,17 +57,30 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
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.TinyRemapperHelper;
-import net.fabricmc.tinyremapper.OutputConsumerPath;
-import net.fabricmc.tinyremapper.TinyRemapper;
-import net.fabricmc.tinyremapper.api.TrClass;
+import net.fabricmc.loom.util.srg.AtRemapper;
+import net.fabricmc.loom.util.srg.CoreModClassRemapper;
+import net.fabricmc.loom.util.srg.InnerClassRemapper;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class MinecraftMappedProvider extends DependencyProvider {
+ 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;
+ protected MinecraftProviderImpl minecraftProvider;
public MinecraftMappedProvider(Project project) {
super(project);
@@ -65,11 +92,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().isForgeAndNotOfficial() && (!getForgeMappedJar().exists() || !getForgeIntermediaryJar().exists() || !getForgeSrgJar().exists() || isRefreshDeps() || isForgeAtDirty)) {
+ needToRemap = true;
+ }
+
+ if (needToRemap) {
if (minecraftMappedJar.exists()) {
minecraftMappedJar.delete();
}
@@ -80,12 +118,37 @@ public class MinecraftMappedProvider extends DependencyProvider {
minecraftIntermediaryJar.delete();
}
+ if (getExtension().isForge() && minecraftSrgJar.exists()) {
+ minecraftSrgJar.delete();
+ }
+
+ if (getExtension().isForgeAndNotOfficial()) {
+ 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,35 +159,139 @@ public class MinecraftMappedProvider extends DependencyProvider {
}
addDependencies(dependency, postPopulationScheduler);
+
+ if (getExtension().isForgeAndNotOfficial()) {
+ getProject().getDependencies().add(Constants.Configurations.FORGE_NAMED,
+ getProject().getDependencies().module("net.minecraftforge-loom:forge-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier() + "/forge"));
+ }
+
+ if (getExtension().isForge()) {
+ getProject().afterEvaluate(project -> {
+ if (!OperatingSystem.isCIBuild()) {
+ try {
+ ForgeSourcesRemapper.addBaseForgeSources(project, getExtension().isForgeAndOfficial());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
}
- private void mapMinecraftJar() throws IOException {
- String fromM = MappingsNamespace.OFFICIAL.toString();
+ private byte[][] inputBytes(Path input) throws IOException {
+ List<byte[]> inputByteList = new ArrayList<>();
+
+ try (FileSystemUtil.Delegate 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);
+ }
+ });
+ }
+ }
+ }
- MappingsProviderImpl mappingsProvider = getExtension().getMappingsProvider();
+ taskCompleter.complete();
+ }
- Path input = minecraftProvider.getMergedJar().toPath();
+ 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);
+ }
+ }
+ }
+ }
+
+ 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();
- for (String toM : Arrays.asList(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString())) {
- final boolean toNamed = MappingsNamespace.NAMED.toString().equals(toM);
- final boolean toIntermediary = MappingsNamespace.INTERMEDIARY.toString().equals(toM);
- final boolean fixSignatures = mappingsProvider.getSignatureFixes() != null;
- final Path output = toNamed ? outputMapped : outputIntermediary;
+ 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().isForgeAndNotOfficial() ? new Info(forgeAssets, inputForge, forgeOutputMapped, forgeOutputIntermediary, forgeOutputSrg) : null;
+
+ Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> pair = TinyRemapperHelper.getTinyRemapper(getProject(), true, builder -> { });
+ TinyRemapper remapper = remapperArray[0] = pair.getLeft();
+
+ assetsOut(input, vanillaAssets);
+
+ if (getExtension().isForgeAndNotOfficial()) {
+ assetsOut(inputForge, forgeAssets);
+ }
+
+ remap(remapper, pair.getMiddle(), pair.getRight(), vanilla, forge, MappingsNamespace.OFFICIAL.toString());
+ }
+
+ 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, Mutable<MemoryMappingTree> mappings, List<TinyRemapper.ApplyVisitorProvider> postApply, Info vanilla, @Nullable Info forge, String fromM) throws IOException {
+ Set<String> classNames = getExtension().isForge() ? InnerClassRemapper.readClassNames(vanilla.input) : null;
+
+ for (String toM : getExtension().isForge() ? Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.SRG.toString(), MappingsNamespace.NAMED.toString()) : Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.NAMED.toString())) {
+ Path output = MappingsNamespace.NAMED.toString().equals(toM) ? vanilla.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? vanilla.outputSrg : vanilla.outputIntermediary;
+ Path outputForge = forge == null ? null : MappingsNamespace.NAMED.toString().equals(toM) ? forge.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? forge.outputSrg : forge.outputIntermediary;
+ InputTag vanillaTag = remapper.createInputTag();
+ InputTag forgeTag = remapper.createInputTag();
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ final boolean fixSignatures = getExtension().getMappingsProvider().getSignatureFixes() != null;
getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")");
- Files.deleteIfExists(output);
+ remapper.readInputs(vanillaTag, vanilla.input);
+
+ if (forge != null) {
+ remapper.readInputs(forgeTag, forge.input);
+ }
+
+ remapper.replaceMappings(getMappings(classNames, fromM, toM, mappings));
+ if (!MappingsNamespace.INTERMEDIARY.toString().equals(toM)) mappings.setValue(null);
+ postApply.clear();
// Bit ugly but whatever, the whole issue is a bit ugly :)
AtomicReference<Map<String, String>> remappedSignatures = new AtomicReference<>();
- TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(getProject(), fromM, toM, true, (builder) -> {
- if (!fixSignatures) {
- return;
- }
- builder.extraPostApplyVisitor(new TinyRemapper.ApplyVisitorProvider() {
+ if (fixSignatures) {
+ postApply.add(new TinyRemapper.ApplyVisitorProvider() {
@Override
public ClassVisitor insertApplyVisitor(TrClass cls, ClassVisitor next) {
return new ClassVisitor(Constants.ASM_VERSION, next) {
@@ -145,20 +312,18 @@ public class MinecraftMappedProvider extends DependencyProvider {
};
}
});
- });
- if (fixSignatures) {
- if (toIntermediary) {
+ if (MappingsNamespace.INTERMEDIARY.toString().equals(toM)) {
// No need to remap, as these are already intermediary
- remappedSignatures.set(mappingsProvider.getSignatureFixes());
+ remappedSignatures.set(getExtension().getMappingsProvider().getSignatureFixes());
} else {
// Remap the sig fixes from intermediary to the target namespace
final Map<String, String> remapped = new HashMap<>();
- final TinyRemapper sigTinyRemapper = TinyRemapperHelper.getTinyRemapper(getProject(), MappingsNamespace.INTERMEDIARY.toString(), toM);
+ final TinyRemapper sigTinyRemapper = TinyRemapperHelper.getTinyRemapper(getProject(), fromM, toM);
final Remapper sigAsmRemapper = sigTinyRemapper.getRemapper();
// Remap the class names and the signatures using a new tiny remapper instance.
- for (Map.Entry<String, String> entry : mappingsProvider.getSignatureFixes().entrySet()) {
+ for (Map.Entry<String, String> entry : getExtension().getMappingsProvider().getSignatureFixes().entrySet()) {
remapped.put(
sigAsmRemapper.map(entry.getKey()),
sigAsmRemapper.mapSignature(entry.getValue(), false)
@@ -170,28 +335,65 @@ public class MinecraftMappedProvider extends DependencyProvider {
}
}
- try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) {
- outputConsumer.addNonClassFiles(input);
- remapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(getProject()));
- 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();
+ OutputRemappingHandler.remap(remapper, vanilla.assets, output, null, vanillaTag);
+
+ if (forge != null) {
+ OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag);
+ }
+
+ getProject().getLogger().lifecycle(":remapped minecraft (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch);
+ remapper.removeInput();
+ mappings.setValue(null);
+
+ if (getExtension().isForge() && !"srg".equals(toM)) {
+ getProject().getLogger().info(":running minecraft finalising tasks");
+
+ MemoryMappingTree yarnWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg();
+ AtRemapper.remap(getProject().getLogger(), output, yarnWithSrg);
+ CoreModClassRemapper.remapJar(output, yarnWithSrg, getProject().getLogger());
+ }
+ }
+ }
+
+ public Set<IMappingProvider> getMappings(@Nullable Set<String> fromClassNames, String fromM, String toM, Mutable<MemoryMappingTree> mappings) throws IOException {
+ Set<IMappingProvider> providers = new HashSet<>();
+ mappings.setValue(getExtension().isForge() ? getExtension().getMappingsProvider().getMappingsWithSrg() : getExtension().getMappingsProvider().getMappings());
+ providers.add(TinyRemapperHelper.create(mappings.getValue(), fromM, toM, true));
+
+ if (getExtension().isForge()) {
+ if (fromClassNames != null) {
+ providers.add(InnerClassRemapper.of(fromClassNames, getExtension().getMappingsProvider().getMappingsWithSrg(), fromM, toM));
}
+ } else {
+ providers.add(out -> TinyRemapperHelper.JSR_TO_JETBRAINS.forEach(out::acceptClass));
}
+
+ return providers;
}
protected void addDependencies(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) {
getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED,
- getProject().getDependencies().module("net.minecraft:minecraft-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()));
+ getProject().getDependencies().module("net.minecraft:" + minecraftProvider.getJarPrefix() + "minecraft-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()));
}
public void initFiles(MinecraftProviderImpl minecraftProvider, MappingsProviderImpl mappingsProvider) {
this.minecraftProvider = minecraftProvider;
minecraftIntermediaryJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-intermediary.jar");
- minecraftMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-mapped.jar");
+ minecraftSrgJar = !getExtension().isForge() ? null : new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-srg.jar");
+ minecraftMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), minecraftProvider.getJarPrefix() + "minecraft-mapped.jar");
+ inputJar = getExtension().isForge() ? mappingsProvider.patchedProvider.getMergedJar() : minecraftProvider.getMergedJar();
+
+ if (getExtension().isForgeAndNotOfficial()) {
+ inputForgeJar = mappingsProvider.patchedProvider.getForgeMergedJar();
+ forgeIntermediaryJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-intermediary.jar");
+ forgeSrgJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-srg.jar");
+ forgeMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-mapped.jar");
+ } else {
+ inputForgeJar = null;
+ forgeIntermediaryJar = null;
+ forgeSrgJar = null;
+ forgeMappedJar = null;
+ }
}
protected File getJarDirectory(File parentDirectory, String type) {
@@ -199,13 +401,17 @@ public class MinecraftMappedProvider extends DependencyProvider {
}
protected String getJarVersionString(String type) {
- return String.format("%s-%s", type, getExtension().getMappingsProvider().mappingsIdentifier());
+ return String.format("%s-%s%s", type, getExtension().getMappingsProvider().mappingsIdentifier(), minecraftProvider.getJarPrefix());
}
public File getIntermediaryJar() {
return minecraftIntermediaryJar;
}
+ public File getSrgJar() {
+ return minecraftSrgJar;
+ }
+
public File getMappedJar() {
return minecraftMappedJar;
}
@@ -214,6 +420,18 @@ public class MinecraftMappedProvider extends DependencyProvider {
return minecraftMappedJar;
}
+ public File getForgeIntermediaryJar() {
+ return forgeIntermediaryJar;
+ }
+
+ public File getForgeSrgJar() {
+ return forgeSrgJar;
+ }
+
+ public File getForgeMappedJar() {
+ return forgeMappedJar;
+ }
+
public File getUnpickedJar() {
return new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-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 87f41e0d..de49c3ba 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.MirrorUtil;
import net.fabricmc.loom.util.HashedDownloadUtil;
-import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
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<ProgressLoggerHelper> 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 ProgressLoggerHelper[] progressLogger = new ProgressLoggerHelper[1];
+ executor.execute(() -> {
+ String assetName = entry.getKey();
+ int end = assetName.lastIndexOf("/") + 1;
- try {
- HashedDownloadUtil.downloadIfInvalid(new URL(MirrorUtil.getResourcesBase(project) + sha1.substring(0, 2) + "/" + sha1), file, sha1, project.getLogger(), true, () -> {
- ProgressLoggerHelper logger = loggers.pollFirst();
+ if (end > 0) {
+ assetName = assetName.substring(end);
+ }
- if (logger == null) {
- //Create a new logger if we need one
- progressLogger[0] = ProgressLoggerHelper.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(MirrorUtil.getResourcesBase(project) + 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(ProgressLoggerHelper::completed);
}
}
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..5e2aa6a9
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/tr/OutputRemappingHandler.java
@@ -0,0 +1,81 @@
+/*
+ * 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.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 (FileSystemUtil.Delegate 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..5b523153
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/sources/ForgeSourcesRemapper.java
@@ -0,0 +1,259 @@
+/*
+ * 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.PrintStream;
+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.apache.commons.io.output.NullOutputStream;
+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.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.configuration.ShowStacktrace;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.build.ModCompileRemapper;
+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.loom.util.TinyRemapperHelper;
+import net.fabricmc.loom.util.ZipUtils;
+import net.fabricmc.lorenztiny.TinyMappingsReader;
+
+public class ForgeSourcesRemapper {
+ public static void addBaseForgeSources(Project project, boolean isOfficial) throws IOException {
+ Path sourcesJar = GenerateSourcesTask.getMappedJarFileWithSuffix(project, "-sources.jar", !isOfficial).toPath();
+
+ if (!Files.exists(sourcesJar)) {
+ addForgeSources(project, sourcesJar);
+ }
+ }
+
+ public static void addForgeSources(Project project, Path sourcesJar) throws IOException {
+ try (FileSystemUtil.Delegate 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.Delegate 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();
+ }
+
+ PrintStream out = System.out;
+ PrintStream err = System.err;
+
+ if (project.getGradle().getStartParameter().getShowStacktrace() == ShowStacktrace.INTERNAL_EXCEPTIONS && project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) >= 0) {
+ System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
+ System.setErr(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
+ }
+
+ remapForgeSourcesInner(project, tmpInput.toPath(), tmpOutput.toPath());
+
+ if (project.getGradle().getStartParameter().getShowStacktrace() == ShowStacktrace.INTERNAL_EXCEPTIONS && project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) >= 0) {
+ System.setOut(out);
+ System.setErr(err);
+ }
+
+ tmpInput.delete();
+ int[] failedToRemap = {0};
+
+ try (FileSystemUtil.Delegate 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("Failed to remap sources for " + entry.getKey());
+ failedToRemap[0]++;
+ }
+ });
+ }
+
+ taskCompleter.complete();
+ }
+
+ tmpOutput.delete();
+
+ if (failedToRemap[0] > 0) {
+ project.getLogger().error("Failed to remap {} forge sources", 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 : TinyRemapperHelper.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());
+
+ if (extension.isForgeAndNotOfficial()) {
+ 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");
+ ZipUtils.unpackAll(tmpInput1, tmpInput);
+ }
+
+ try (FileSystemUtil.Delegate 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.Delegate 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;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java
new file mode 100644
index 00000000..25da6717
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java
@@ -0,0 +1,136 @@
+/*
+ * 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.extension;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Project;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.provider.Property;
+import org.gradle.api.provider.SetProperty;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.api.ForgeExtensionAPI;
+import net.fabricmc.loom.api.ForgeLocalMod;
+import net.fabricmc.loom.configuration.ide.RunConfigSettings;
+
+public class ForgeExtensionImpl implements ForgeExtensionAPI {
+ private final LoomGradleExtension extension;
+ private final Property<Boolean> convertAccessWideners;
+ private final SetProperty<String> extraAccessWideners;
+ private final ConfigurableFileCollection accessTransformers;
+ private final SetProperty<String> mixinConfigs;
+ private final Property<Boolean> useCustomMixin;
+ private final List<String> dataGenMods = new ArrayList<>(); // not a property because it has custom adding logic
+ private final NamedDomainObjectContainer<ForgeLocalMod> localMods;
+
+ @Inject
+ public ForgeExtensionImpl(Project project, LoomGradleExtension extension) {
+ this.extension = extension;
+ convertAccessWideners = project.getObjects().property(Boolean.class).convention(false);
+ extraAccessWideners = project.getObjects().setProperty(String.class).empty();
+ accessTransformers = project.getObjects().fileCollection();
+ mixinConfigs = project.getObjects().setProperty(String.class).empty();
+ useCustomMixin = project.getObjects().property(Boolean.class).convention(true);
+ localMods = project.container(ForgeLocalMod.class,
+ baseName -> new ForgeLocalMod(project, baseName, new ArrayList<>()));
+
+ // Create default mod from main source set
+ localMods(mod -> mod.create("main").add("main"));
+ }
+
+ @Override
+ public Property<Boolean> getConvertAccessWideners() {
+ return convertAccessWideners;
+ }
+
+ @Override
+ public SetProperty<String> getExtraAccessWideners() {
+ return extraAccessWideners;
+ }
+
+ @Override
+ public ConfigurableFileCollection getAccessTransformers() {
+ return accessTransformers;
+ }
+
+ @Override
+ public void accessTransformer(Object file) {
+ accessTransformers.from(file);
+ }
+
+ @Override
+ public SetProperty<String> getMixinConfigs() {
+ return mixinConfigs;
+ }
+
+ @Override
+ public void mixinConfigs(String... mixinConfigs) {
+ this.mixinConfigs.addAll(mixinConfigs);
+ }
+
+ @Override
+ public Property<Boolean> getUseCustomMixin() {
+ return useCustomMixin;
+ }
+
+ @Override
+ public List<String> getDataGenMods() {
+ // unmod list prevents uncontrolled additions (we want to create the run config too)
+ return Collections.unmodifiableList(dataGenMods);
+ }
+
+ @SuppressWarnings("Convert2Lambda")
+ @Override
+ public void dataGen(Action<DataGenConsumer> action) {
+ action.execute(new DataGenConsumer() {
+ @Override
+ public void mod(String... modIds) {
+ dataGenMods.addAll(Arrays.asList(modIds));
+
+ if (modIds.length > 0 && extension.getRunConfigs().findByName("data") == null) {
+ extension.getRunConfigs().create("data", RunConfigSettings::data);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void localMods(Action<NamedDomainObjectContainer<ForgeLocalMod>> action) {
+ action.execute(localMods);
+ }
+
+ @Override
+ public NamedDomainObjectContainer<ForgeLocalMod> getLocalMods() {
+ return localMods;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
index f833e52e..d3f6d671 100644
--- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java
@@ -24,6 +24,14 @@
package net.fabricmc.loom.extension;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import com.google.common.base.Suppliers;
import org.gradle.api.Action;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
@@ -32,13 +40,18 @@ import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
+import org.gradle.api.provider.Provider;
import org.gradle.api.publish.maven.MavenPublication;
+import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.MixinExtensionAPI;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
+import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
+import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
+import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.configuration.mods.ModVersionParser;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext;
@@ -46,11 +59,15 @@ import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl;
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency;
import net.fabricmc.loom.util.DeprecationHelper;
+import net.fabricmc.loom.util.ModPlatform;
/**
* This class implements the public extension api.
*/
public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionAPI {
+ private static final String FORGE_PROPERTY = "loom.forge";
+ private static final String PLATFORM_PROPERTY = "loom.platform";
+
protected final DeprecationHelper deprecationHelper;
protected final ListProperty<LoomDecompiler> decompilers;
protected final ListProperty<JarProcessor> jarProcessors;
@@ -67,6 +84,17 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
private NamedDomainObjectContainer<RunConfigSettings> runConfigs;
+ // ===================
+ // Architectury Loom
+ // ===================
+ private final ListProperty<ArchitecturyLoomDecompiler> archDecompilers;
+ private Provider<ModPlatform> platform;
+ private boolean silentMojangMappingsLicense = false;
+ public Boolean generateSrgTiny = null;
+ private final List<String> tasksBeforeRun = Collections.synchronizedList(new ArrayList<>());
+ public final List<Consumer<RunConfig>> settingsPostEdit = new ArrayList<>();
+ private NamedDomainObjectContainer<LaunchProviderSettings> launchConfigs;
+
protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) {
this.runConfigs = project.container(RunConfigSettings.class,
baseName -> new RunConfigSettings(project, baseName));
@@ -92,6 +120,26 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
this.versionParser = new ModVersionParser(project);
this.deprecationHelper = new DeprecationHelper.ProjectBased(project);
+ this.platform = project.provider(Suppliers.memoize(() -> {
+ Object platformProperty = project.findProperty(PLATFORM_PROPERTY);
+
+ if (platformProperty != null) {
+ return ModPlatform.valueOf(Objects.toString(platformProperty).toUpperCase(Locale.ROOT));
+ }
+
+ Object forgeProperty = project.findProperty(FORGE_PROPERTY);
+
+ if (forgeProperty != null) {
+ project.getLogger().warn("Project " + project.getPath() + " is using property " + FORGE_PROPERTY + " to enable forge mode. Please use '" + PLATFORM_PROPERTY + " = forge' instead!");
+ return Boolean.parseBoolean(Objects.toString(forgeProperty)) ? ModPlatform.FORGE : ModPlatform.FABRIC;
+ }
+
+ return ModPlatform.FABRIC;
+ })::get);
+ this.launchConfigs = project.container(LaunchProviderSettings.class,
+ baseName -> new LaunchProviderSettings(project, baseName));
+ this.archDecompilers = project.getObjects().listProperty(ArchitecturyLoomDecompiler.class)
+ .empty();
}
@Override
@@ -121,12 +169,14 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
@Override
public Dependency layered(Action<LayeredMappingSpecBuilder> action) {
- LayeredMappingSpecBuilderImpl builder = new LayeredMappingSpecBuilderImpl();
+ LayeredMappingSpecBuilderImpl builder = new LayeredMappingSpecBuilderImpl(this);
action.execute(builder);
LayeredMappingSpec builtSpec = builder.build();
return new LayeredMappingsDependency(getProject(), new GradleMappingContext(getProject(), builtSpec.getVersion().replace("+", "_").replace(".", "_")), builtSpec, builtSpec.getVersion());
}
+ protected abstract String getMinecraftVersion();
+
@Override
public Property<Boolean> getRemapArchives() {
return remapArchives;
@@ -186,6 +236,65 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication);
}
+ @Override
+ public void silentMojangMappingsLicense() {
+ this.silentMojangMappingsLicense = true;
+ }
+
+ @Override
+ public boolean isSilentMojangMappingsLicenseEnabled() {
+ return silentMojangMappingsLicense;
+ }
+
+ @Override
+ public Provider<ModPlatform> getPlatform() {
+ return platform;
+ }
+
+ @Override
+ public void setGenerateSrgTiny(Boolean generateSrgTiny) {
+ this.generateSrgTiny = generateSrgTiny;
+ }
+
+ @Override
+ public boolean shouldGenerateSrgTiny() {
+ if (generateSrgTiny != null) {
+ return generateSrgTiny;
+ }
+
+ return isForge();
+ }
+
+ @Override
+ public void launches(Action<NamedDomainObjectContainer<LaunchProviderSettings>> action) {
+ action.execute(launchConfigs);
+ }
+
+ @Override
+ public NamedDomainObjectContainer<LaunchProviderSettings> getLaunchConfigs() {
+ return launchConfigs;
+ }
+
+ @Override
+ public List<String> getTasksBeforeRun() {
+ return tasksBeforeRun;
+ }
+
+ @Override
+ public List<Consumer<RunConfig>> getSettingsPostEdit() {
+ return settingsPostEdit;
+ }
+
+ @Override
+ public void forge(Action<ForgeExtensionAPI> action) {
+ action.execute(getForge());
+ }
+
+ @Override
+ public ListProperty<ArchitecturyLoomDecompiler> getArchGameDecompilers() {
+ return archDecompilers;
+ }
+
// This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods
private final class EnsureCompile extends LoomGradleExtensionApiImpl {
private EnsureCompile() {
@@ -212,5 +321,15 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
public MixinExtension getMixin() {
throw new RuntimeException("Yeah... something is really wrong");
}
+
+ @Override
+ protected String getMinecraftVersion() {
+ throw new RuntimeException("Yeah... something is really wrong");
+ }
+
+ @Override
+ public ForgeExtensionAPI getForge() {
+ throw new RuntimeException("Yeah... something is really wrong");
+ }
}
}
diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
index da0fca73..9b93c066 100644
--- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java
@@ -35,6 +35,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
+import com.google.common.base.Suppliers;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury;
import org.gradle.api.NamedDomainObjectProvider;
@@ -43,16 +44,20 @@ import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection;
import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.configuration.InstallerData;
import net.fabricmc.loom.configuration.LoomDependencyManager;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
+import net.fabricmc.loom.util.ModPlatform;
+import net.fabricmc.loom.util.function.LazyBool;
public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implements LoomGradleExtension {
private final Project project;
private final MixinExtension mixinApExtension;
private final LoomFiles loomFiles;
private final ConfigurableFileCollection unmappedMods;
+ private final Supplier<ForgeExtensionAPI> forgeExtension;
private final Set<File> mixinMappings = Collections.synchronizedSet(new HashSet<>());
private final MappingSet[] srcMappingCache = new MappingSet[2];
@@ -64,6 +69,12 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private JarProcessorManager jarProcessorManager;
private InstallerData installerData;
+ // +-------------------+
+ // | Architectury Loom |
+ // +-------------------+
+ private static final String INCLUDE_PROPERTY = "loom.forge.include";
+ private final LazyBool supportsInclude;
+
public LoomGradleExtensionImpl(Project project, LoomFiles files) {
super(project, files);
this.project = project;
@@ -71,6 +82,8 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
this.mixinApExtension = project.getObjects().newInstance(MixinExtensionImpl.class, project);
this.loomFiles = files;
this.unmappedMods = project.files();
+ this.forgeExtension = Suppliers.memoize(() -> isForge() ? project.getObjects().newInstance(ForgeExtensionImpl.class, project, this) : null);
+ this.supportsInclude = new LazyBool(() -> Boolean.parseBoolean(Objects.toString(project.findProperty(INCLUDE_PROPERTY))));
}
@Override
@@ -117,11 +130,13 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
@Override
public MappingSet getOrCreateSrcMappingCache(int id, Supplier<MappingSet> factory) {
+ if (id < 0 || id >= srcMappingCache.length) return factory.get();
return srcMappingCache[id] != null ? srcMappingCache[id] : (srcMappingCache[id] = factory.get());
}
@Override
public Mercury getOrCreateSrcMercuryCache(int id, Supplier<Mercury> factory) {
+ if (id < 0 || id >= srcMercuryCache.length) return factory.get();
return srcMercuryCache[id] != null ? srcMercuryCache[id] : (srcMercuryCache[id] = factory.get());
}
@@ -182,4 +197,20 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
public void addTransitiveAccessWideners(List<AccessWidenerFile> accessWidenerFiles) {
transitiveAccessWideners.addAll(accessWidenerFiles);
}
+
+ @Override
+ protected String getMinecraftVersion() {
+ return getMinecraftProvider().minecraftVersion();
+ }
+
+ @Override
+ public ForgeExtensionAPI getForge() {
+ ModPlatform.assertPlatform(this, ModPlatform.FORGE);
+ return forgeExtension.get();
+ }
+
+ @Override
+ public boolean supportsInclude() {
+ return !isForge() || supportsInclude.getAsBoolean();
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java
index 4ffea879..bd33fd62 100644
--- a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java
+++ b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java
@@ -24,6 +24,9 @@
package net.fabricmc.loom.extension;
+import java.util.List;
+import java.util.function.Consumer;
+
import org.gradle.api.Action;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.artifacts.Dependency;
@@ -31,15 +34,21 @@ import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
+import org.gradle.api.provider.Provider;
import org.gradle.api.publish.maven.MavenPublication;
+import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.MixinExtensionAPI;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
+import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler;
import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder;
+import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
+import net.fabricmc.loom.configuration.launch.LaunchProviderSettings;
import net.fabricmc.loom.configuration.processors.JarProcessor;
import net.fabricmc.loom.util.DeprecationHelper;
+import net.fabricmc.loom.util.ModPlatform;
public class MinecraftGradleExtension implements LoomGradleExtensionAPI {
private final LoomGradleExtensionAPI parent;
@@ -162,4 +171,76 @@ public class MinecraftGradleExtension implements LoomGradleExtensionAPI {
reportDeprecation();
return parent.getIntermediaryUrl();
}
+
+ @Override
+ public ListProperty<ArchitecturyLoomDecompiler> getArchGameDecompilers() {
+ reportDeprecation();
+ return parent.getArchGameDecompilers();
+ }
+
+ @Override
+ public void silentMojangMappingsLicense() {
+ reportDeprecation();
+ parent.silentMojangMappingsLicense();
+ }
+
+ @Override
+ public boolean isSilentMojangMappingsLicenseEnabled() {
+ reportDeprecation();
+ return parent.isSilentMojangMappingsLicenseEnabled();
+ }
+
+ @Override
+ public Provider<ModPlatform> getPlatform() {
+ reportDeprecation();
+ return parent.getPlatform();
+ }
+
+ @Override
+ public void setGenerateSrgTiny(Boolean generateSrgTiny) {
+ reportDeprecation();
+ parent.setGenerateSrgTiny(generateSrgTiny);
+ }
+
+ @Override
+ public boolean shouldGenerateSrgTiny() {
+ reportDeprecation();
+ return parent.shouldGenerateSrgTiny();
+ }
+
+ @Override
+ public void launches(Action<NamedDomainObjectContainer<LaunchProviderSettings>> action) {
+ reportDeprecation();
+ parent.launches(action);
+ }
+
+ @Override
+ public NamedDomainObjectContainer<LaunchProviderSettings> getLaunchConfigs() {
+ reportDeprecation();
+ return parent.getLaunchConfigs();
+ }
+
+ @Override
+ public List<String> getTasksBeforeRun() {
+ reportDeprecation();
+ return parent.getTasksBeforeRun();
+ }
+
+ @Override
+ public List<Consumer<RunConfig>> getSettingsPostEdit() {
+ reportDeprecation();
+ return parent.getSettingsPostEdit();
+ }
+
+ @Override
+ public ForgeExtensionAPI getForge() {
+ reportDeprecation();
+ return parent.getForge();
+ }
+
+ @Override
+ public void forge(Action<ForgeExtensionAPI> action) {
+ reportDeprecation();
+ parent.forge(action);
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java
index cf958d99..d0e3950d 100644
--- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionApiImpl.java
@@ -44,6 +44,7 @@ public abstract class MixinExtensionApiImpl implements MixinExtensionAPI {
public MixinExtensionApiImpl(Project project) {
this.project = Objects.requireNonNull(project);
this.useMixinAp = project.getObjects().property(Boolean.class)
+ // .convention(project.provider(() -> LoomGradleExtension.get(project).isForge()));
.convention(true);
}
diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java
index 074251e4..a96c911a 100644
--- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java
@@ -68,6 +68,11 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx
private String getDefaultMixinRefmapName() {
String defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-refmap.json";
+
+ if (project.getRootProject() != project) {
+ defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-" + project.getPath().replaceFirst(":", "").replace(':', '_') + "-refmap.json";
+ }
+
project.getLogger().info("Could not find refmap definition, will be using default name: " + defaultRefmapName);
return defaultRefmapName;
}
diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java
index 6bb63e53..f3c2b7bd 100644
--- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java
+++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java
@@ -51,6 +51,7 @@ public abstract class AbstractRunTask extends JavaExec {
@Override
public void exec() {
setWorkingDir(new File(getProject().getRootDir(), config.runDir));
+ environment(config.envVariables);
super.exec();
}
diff --git a/src/main/java/net/fabricmc/loom/task/ArchitecturyGenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/ArchitecturyGenerateSourcesTask.java
new file mode 100644
index 00000000..402cee1e
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/task/ArchitecturyGenerateSourcesTask.java
@@ -0,0 +1,96 @@
+/*
+ * 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.task;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+
+import javax.inject.Inject;
+
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.provider.MapProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import net.fabricmc.loom.api.decompilers.DecompilationMetadata;
+import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler;
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.OperatingSystem;
+
+public abstract class ArchitecturyGenerateSourcesTask extends AbstractLoomTask {
+ private final ArchitecturyLoomDecompiler decompiler;
+
+ @InputFile
+ public abstract RegularFileProperty getInputJar();
+
+ @Input
+ public abstract MapProperty<String, String> getOptions();
+
+ @Inject
+ public ArchitecturyGenerateSourcesTask(ArchitecturyLoomDecompiler decompiler) {
+ this.decompiler = decompiler;
+ getOutputs().upToDateWhen((o) -> false);
+ getOptions().finalizeValueOnRead();
+ }
+
+ @TaskAction
+ public void run() throws IOException {
+ if (!OperatingSystem.is64Bit()) {
+ throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements.");
+ }
+
+ // TODO: Need a good way to not keep a duplicated code for this
+ Path compiledJar = getInputJar().get().getAsFile().toPath();
+ Path runtimeJar = getExtension().getMappingsProvider().mappedProvider.getMappedJar().toPath();
+ Path sourcesDestination = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), "-sources.jar").toPath();
+ Path linemapDestination = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), "-sources.lmap").toPath();
+ DecompilationMetadata metadata = new DecompilationMetadata(
+ Runtime.getRuntime().availableProcessors(),
+ GenerateSourcesTask.getMappings(getProject(), getExtension()),
+ GenerateSourcesTask.DecompileAction.toPaths(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES)),
+ getLogger()::info,
+ getOptions().get()
+ );
+
+ decompiler.create(getProject()).decompile(compiledJar, sourcesDestination, linemapDestination, metadata);
+
+ // Apply linemap
+ if (Files.exists(linemapDestination)) {
+ Path linemapJar = GenerateSourcesTask.getMappedJarFileWithSuffix(getProject(), "-linemapped.jar").toPath();
+
+ try {
+ GenerateSourcesTask.DecompileAction.remapLineNumbers(getLogger()::info, runtimeJar, linemapDestination, linemapJar);
+ Files.copy(linemapJar, runtimeJar, StandardCopyOption.REPLACE_EXISTING);
+ Files.delete(linemapJar);
+ } catch (Exception e) {
+ throw new RuntimeException("Could not remap line numbers", e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java
index 1e529bdc..b430f5cc 100644
--- a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java
+++ b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java
@@ -41,6 +41,7 @@ public class GenEclipseRunsTask extends AbstractLoomTask {
public void genRuns() throws IOException {
EclipseModel eclipseModel = getProject().getExtensions().getByType(EclipseModel.class);
LoomGradleExtension extension = getExtension();
+ File dataRunConfigs = new File(getProject().getRootDir(), eclipseModel.getProject().getName() + "_data.launch");
for (RunConfigSettings settings : extension.getRunConfigs()) {
if (!settings.isIdeConfigGenerated()) {
diff --git a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java
index ab6fc5fa..476dcd15 100644
--- a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java
+++ b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java
@@ -29,12 +29,20 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
import org.apache.commons.io.FileUtils;
+import org.apache.tools.ant.taskdefs.condition.Os;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;
+import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
@@ -46,8 +54,12 @@ import net.fabricmc.loom.configuration.ide.RunConfigSettings;
public class GenVsCodeProjectTask extends AbstractLoomTask {
@TaskAction
public void genRuns() {
- Project project = getProject();
- File projectDir = project.file(".vscode");
+ clean(getProject());
+ generate(getProject());
+ }
+
+ public static void clean(Project project) {
+ File projectDir = project.getRootProject().file(".vscode");
if (!projectDir.exists()) {
projectDir.mkdir();
@@ -58,15 +70,39 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {
if (launchJson.exists()) {
launchJson.delete();
}
+ }
+
+ public static void generate(Project project) {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+ File projectDir = project.getRootProject().file(".vscode");
+
+ if (!projectDir.exists()) {
+ projectDir.mkdir();
+ }
+
+ File launchJson = new File(projectDir, "launch.json");
+ File tasksJson = new File(projectDir, "tasks.json");
+
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
- VsCodeLaunch launch = new VsCodeLaunch();
+ VsCodeLaunch launch;
- for (RunConfigSettings settings : getExtension().getRunConfigs()) {
+ if (launchJson.exists()) {
+ try {
+ launch = gson.fromJson(FileUtils.readFileToString(launchJson, StandardCharsets.UTF_8), VsCodeLaunch.class);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to read launch.json", e);
+ }
+ } else {
+ launch = new VsCodeLaunch();
+ }
+
+ for (RunConfigSettings settings : extension.getRunConfigs()) {
if (!settings.isIdeConfigGenerated()) {
continue;
}
- launch.add(RunConfig.runConfig(project, settings));
+ launch.add(project, RunConfig.runConfig(project, settings));
settings.makeRunDir();
}
@@ -77,19 +113,71 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {
} catch (IOException e) {
throw new RuntimeException("Failed to write launch.json", e);
}
+
+ VsCodeTasks tasks;
+
+ if (tasksJson.exists()) {
+ try {
+ tasks = gson.fromJson(FileUtils.readFileToString(tasksJson, StandardCharsets.UTF_8), VsCodeTasks.class);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to read launch.json", e);
+ }
+ } else {
+ tasks = new VsCodeTasks();
+ }
+
+ for (VsCodeConfiguration configuration : launch.configurations) {
+ if (configuration.preLaunchTask != null && configuration.tasksBeforeRun != null) {
+ String prefix = Os.isFamily(Os.FAMILY_WINDOWS) ? "gradlew.bat" : "./gradlew";
+ tasks.add(new VsCodeTask(configuration.preLaunchTask, prefix + " " + configuration.tasksBeforeRun.stream()
+ .map(s -> {
+ int i = s.indexOf('/');
+ return i == -1 ? s : s.substring(i + 1);
+ }).collect(Collectors.joining(" ")), "shell", new String[0]));
+ }
+ }
+
+ if (!tasks.tasks.isEmpty()) {
+ String jsonTasks = gson.toJson(tasks);
+
+ try {
+ FileUtils.writeStringToFile(tasksJson, jsonTasks, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to write tasks.json", e);
+ }
+ }
}
- private class VsCodeLaunch {
+ private static class VsCodeLaunch {
public String version = "0.2.0";
public List<VsCodeConfiguration> configurations = new ArrayList<>();
- public void add(RunConfig runConfig) {
- configurations.add(new VsCodeConfiguration(runConfig));
+ public void add(Project project, RunConfig runConfig) {
+ if (configurations.stream().noneMatch(config -> Objects.equals(config.name, runConfig.configName))) {
+ VsCodeConfiguration configuration = new VsCodeConfiguration(project, runConfig);
+ configurations.add(configuration);
+
+ if (!configuration.tasksBeforeRun.isEmpty()) {
+ configuration.preLaunchTask = "generated_" + runConfig.configName;
+ }
+ }
+ }
+ }
+
+ private static class VsCodeTasks {
+ public String version = "2.0.0";
+ public List<VsCodeTask> tasks = new ArrayList<>();
+
+ public void add(VsCodeTask vsCodeTask) {
+ if (tasks.stream().noneMatch(task -> Objects.equals(task.label, vsCodeTask.label))) {
+ tasks.add(vsCodeTask);
+ }
}
}
@SuppressWarnings("unused")
- private class VsCodeConfiguration {
+ private static class VsCodeConfiguration {
+ public transient Project project;
public String type = "java";
public String name;
public String request = "launch";
@@ -99,21 +187,43 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {
public String mainClass;
public String vmArgs;
public String args;
+ public Map<String, String> env = new LinkedHashMap<>();
+ public transient List<String> tasksBeforeRun = new ArrayList<>();
+ public String preLaunchTask = null;
+ public String projectName = null;
- VsCodeConfiguration(RunConfig runConfig) {
+ VsCodeConfiguration(Project project, RunConfig runConfig) {
this.name = runConfig.configName;
this.mainClass = runConfig.mainClass;
this.vmArgs = RunConfig.joinArguments(runConfig.vmArgs);
this.args = RunConfig.joinArguments(runConfig.programArgs);
this.cwd = "${workspaceFolder}/" + runConfig.runDir;
+ this.projectName = runConfig.vscodeProjectName;
+ this.env.putAll(runConfig.envVariables);
+ this.tasksBeforeRun.addAll(runConfig.vscodeBeforeRun);
- if (getProject().getRootProject() != getProject()) {
- Path rootPath = getProject().getRootDir().toPath();
- Path projectPath = getProject().getProjectDir().toPath();
+ if (project.getRootProject() != project) {
+ Path rootPath = project.getRootDir().toPath();
+ Path projectPath = project.getProjectDir().toPath();
String relativePath = rootPath.relativize(projectPath).toString();
this.cwd = "${workspaceFolder}/%s/%s".formatted(relativePath, runConfig.runDir);
}
}
}
+
+ private static class VsCodeTask {
+ public String label;
+ public String command;
+ public String type;
+ public String[] args;
+ public String group = "build";
+
+ VsCodeTask(String label, String command, String type, String[] args) {
+ this.label = label;
+ this.command = command;
+ this.type = type;
+ this.args = args;
+ }
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
index 9a127d86..ab4f6929 100644
--- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
+++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java
@@ -41,7 +41,9 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
+import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
@@ -146,7 +148,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
params.getSourcesDestinationJar().set(getMappedJarFileWithSuffix("-sources.jar"));
params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap"));
params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar"));
- params.getMappings().set(getMappings().toFile());
+ params.getMappings().set(getMappings(getProject(), getExtension()).toFile());
if (ipcPath != null) {
params.getIPCPath().set(ipcPath.toFile());
@@ -271,7 +273,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
}
- private void remapLineNumbers(IOStringConsumer logger, Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException {
+ static void remapLineNumbers(IOStringConsumer logger, Path oldCompiledJar, Path linemap, Path linemappedJarDestination) throws IOException {
LineNumberRemapper remapper = new LineNumberRemapper();
remapper.readMappings(linemap.toFile());
@@ -282,14 +284,26 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
}
private Collection<Path> getLibraries() {
- return getParameters().getClassPath().getFiles().stream().map(File::toPath).collect(Collectors.toSet());
+ return toPaths(getParameters().getClassPath());
+ }
+
+ static Collection<Path> toPaths(FileCollection files) {
+ return files.getFiles().stream().map(File::toPath).collect(Collectors.toSet());
}
}
private File getMappedJarFileWithSuffix(String suffix) {
- LoomGradleExtension extension = LoomGradleExtension.get(getProject());
+ return getMappedJarFileWithSuffix(getProject(), suffix);
+ }
+
+ public static File getMappedJarFileWithSuffix(Project project, String suffix) {
+ return getMappedJarFileWithSuffix(project, suffix, false);
+ }
+
+ public static File getMappedJarFileWithSuffix(Project project, String suffix, boolean forgeJar) {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
- File mappedJar = mappingsProvider.mappedProvider.getMappedJar();
+ File mappedJar = forgeJar ? mappingsProvider.mappedProvider.getForgeMappedJar() : mappingsProvider.mappedProvider.getMappedJar();
String path = mappedJar.getAbsolutePath();
if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) {
@@ -299,11 +313,11 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
return new File(path.substring(0, path.length() - 4) + suffix);
}
- private Path getMappings() {
- Path baseMappings = getExtension().getMappingsProvider().tinyMappings;
+ static Path getMappings(Project project, LoomGradleExtension extension) {
+ Path baseMappings = extension.isForge() ? extension.getMappingsProvider().tinyMappingsWithSrg : extension.getMappingsProvider().tinyMappings;
- if (getExtension().getEnableTransitiveAccessWideners().get()) {
- List<AccessWidenerFile> accessWideners = getExtension().getTransitiveAccessWideners();
+ if (extension.getEnableTransitiveAccessWideners().get()) {
+ List<AccessWidenerFile> accessWideners = extension.getTransitiveAccessWideners();
if (accessWideners.isEmpty()) {
return baseMappings;
@@ -317,7 +331,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
throw new RuntimeException("Failed to create temp file", e);
}
- TransitiveAccessWidenerMappingsProcessor.process(baseMappings, outputMappings, accessWideners, getProject().getLogger());
+ TransitiveAccessWidenerMappingsProcessor.process(baseMappings, outputMappings, accessWideners, project.getLogger());
return outputMappings;
}
diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java
index e914046f..47ea0eb6 100644
--- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java
+++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java
@@ -25,6 +25,8 @@
package net.fabricmc.loom.task;
import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
import com.google.common.base.Preconditions;
import org.gradle.api.Project;
@@ -32,7 +34,9 @@ import org.gradle.api.tasks.TaskContainer;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.decompilers.LoomDecompiler;
+import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
+import net.fabricmc.loom.configuration.ide.SetupIntelijRunConfigs;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.Constants;
@@ -65,6 +69,7 @@ public final class LoomTasks {
registerIDETasks(tasks);
registerRunTasks(tasks, project);
+ registerLaunchSettings(project);
registerDecompileTasks(tasks, project);
}
@@ -91,6 +96,19 @@ public final class LoomTasks {
t.dependsOn("downloadAssets");
t.setGroup(Constants.TaskGroup.IDE);
});
+
+ tasks.register("genIntelliJRuns", AbstractLoomTask.class, t -> {
+ t.setDescription("Generates IntelliJ IDEA launch configurations.");
+ t.dependsOn("downloadAssets");
+ t.setGroup("ide");
+ t.doLast(task -> {
+ try {
+ SetupIntelijRunConfigs.generate(task.getProject(), true);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
+ });
}
private static void registerRunTasks(TaskContainer tasks, Project project) {
@@ -115,6 +133,17 @@ public final class LoomTasks {
extension.getRunConfigs().create("server", RunConfigSettings::server);
}
+ private static void registerLaunchSettings(Project project) {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+ Preconditions.checkArgument(extension.getLaunchConfigs().size() == 0, "Launch configurations must not be registered before loom");
+ extension.getLaunchConfigs().create("client");
+ extension.getLaunchConfigs().create("server");
+
+ if (extension.isForge()) {
+ extension.getLaunchConfigs().create("data");
+ }
+ }
+
private static void registerDecompileTasks(TaskContainer tasks, Project project) {
LoomGradleExtension extension = LoomGradleExtension.get(project);
@@ -160,6 +189,20 @@ public final class LoomTasks {
});
}
+ for (ArchitecturyLoomDecompiler decompiler : extension.getArchGameDecompilers().get()) {
+ String taskName = "genSourcesWith" + decompiler.name();
+ // Decompiler will be passed to the constructor of ArchitecturyGenerateSourcesTask
+ tasks.register(taskName, ArchitecturyGenerateSourcesTask.class, decompiler).configure(task -> {
+ task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name()));
+ task.setGroup(Constants.TaskGroup.FABRIC);
+ task.getInputJar().set(inputJar);
+
+ if (mappingsProvider.hasUnpickDefinitions()) {
+ task.dependsOn(tasks.getByName("unpickJar"));
+ }
+ });
+ }
+
tasks.register("genSources", task -> {
task.setDescription("Decompile minecraft using the default decompiler.");
task.setGroup(Constants.TaskGroup.FABRIC);
diff --git a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java
index c976dded..f7181637 100644
--- a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java
+++ b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java
@@ -101,7 +101,7 @@ public class MigrateMappingsTask extends AbstractLoomTask {
try {
MemoryMappingTree currentMappings = mappingsProvider.getMappings();
MemoryMappingTree targetMappings = getMappings(mappings);
- migrateMappings(project, extension.getMinecraftMappedProvider(), inputDir, outputDir, currentMappings, targetMappings);
+ migrateMappings(project, extension, extension.getMinecraftMappedProvider(), inputDir, outputDir, currentMappings, targetMappings);
project.getLogger().lifecycle(":remapped project written to " + outputDir.toAbsolutePath());
} catch (IOException e) {
throw new IllegalArgumentException("Error while loading mappings", e);
@@ -157,8 +157,8 @@ public class MigrateMappingsTask extends AbstractLoomTask {
return mappingTree;
}
- private static void migrateMappings(Project project, MinecraftMappedProvider minecraftMappedProvider,
- Path inputDir, Path outputDir, MemoryMappingTree currentMappings, MemoryMappingTree targetMappings
+ private static void migrateMappings(Project project, LoomGradleExtension extension, MinecraftMappedProvider minecraftMappedProvider,
+ Path inputDir, Path outputDir, MemoryMappingTree currentMappings, MemoryMappingTree targetMappings
) throws IOException {
project.getLogger().info(":joining mappings");
@@ -182,6 +182,16 @@ public class MigrateMappingsTask extends AbstractLoomTask {
mercury.getClassPath().add(minecraftMappedProvider.getMappedJar().toPath());
mercury.getClassPath().add(minecraftMappedProvider.getIntermediaryJar().toPath());
+ if (extension.isForge()) {
+ mercury.getClassPath().add(minecraftMappedProvider.getSrgJar().toPath());
+
+ if (extension.isForgeAndNotOfficial()) {
+ mercury.getClassPath().add(minecraftMappedProvider.getForgeMappedJar().toPath());
+ mercury.getClassPath().add(minecraftMappedProvider.getForgeIntermediaryJar().toPath());
+ mercury.getClassPath().add(minecraftMappedProvider.getForgeSrgJar().toPath());
+ }
+ }
+
mercury.getProcessors().add(MercuryRemapper.create(mappingSet));
try {
diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java
index 600d6821..1ced8299 100644
--- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java
+++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java
@@ -24,23 +24,37 @@
package net.fabricmc.loom.task;
+import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.jar.Manifest;
import com.google.common.base.Preconditions;
+import dev.architectury.tinyremapper.IMappingProvider;
+import dev.architectury.tinyremapper.TinyRemapper;
+import dev.architectury.tinyremapper.TinyUtils;
+import dev.architectury.tinyremapper.extension.mixin.MixinExtension;
+import org.cadixdev.at.AccessTransformSet;
+import org.cadixdev.at.io.AccessTransformFormats;
+import org.cadixdev.lorenz.MappingSet;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
@@ -48,6 +62,7 @@ import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Property;
+import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.TaskAction;
@@ -55,9 +70,9 @@ import org.gradle.jvm.tasks.Jar;
import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.LoomGradleExtension;
-import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.build.JarRemapper;
import net.fabricmc.loom.build.MixinRefmapHelper;
+import net.fabricmc.loom.build.nesting.EmptyNestedJarProvider;
import net.fabricmc.loom.build.nesting.JarNester;
import net.fabricmc.loom.build.nesting.MergedNestedJarProvider;
import net.fabricmc.loom.build.nesting.NestedDependencyProvider;
@@ -68,13 +83,16 @@ import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.LfWriter;
+import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.ZipReprocessorUtil;
import net.fabricmc.loom.util.ZipUtils;
+import net.fabricmc.loom.util.aw2at.Aw2At;
+import net.fabricmc.lorenztiny.TinyMappingsReader;
+import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.stitch.util.Pair;
-import net.fabricmc.tinyremapper.TinyRemapper;
-import net.fabricmc.tinyremapper.TinyUtils;
-import net.fabricmc.tinyremapper.extension.mixin.MixinExtension;
public class RemapJarTask extends Jar {
private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
@@ -84,6 +102,9 @@ public class RemapJarTask extends Jar {
private final Property<Boolean> addDefaultNestedDependencies;
private final Property<Boolean> remapAccessWidener;
private final List<Action<TinyRemapper.Builder>> remapOptions = new ArrayList<>();
+ private final Property<String> fromM;
+ private final Property<String> toM;
+ private final SetProperty<String> atAccessWideners;
public JarRemapper jarRemapper;
private FileCollection classpath;
private final Set<Object> nestedPaths = new LinkedHashSet<>();
@@ -98,6 +119,12 @@ public class RemapJarTask extends Jar {
.convention(true);
remapAccessWidener = getProject().getObjects().property(Boolean.class)
.convention(false);
+ fromM = getProject().getObjects().property(String.class)
+ .convention("named");
+ toM = getProject().getObjects().property(String.class)
+ .convention(SourceRemapper.intermediary(getProject()));
+ atAccessWideners = getProject().getObjects().setProperty(String.class)
+ .empty();
if (!extension.getMixin().getUseLegacyMixinAp().get()) {
remapOptions.add(b -> b.extension(new MixinExtension()));
@@ -116,8 +143,10 @@ public class RemapJarTask extends Jar {
scheduleRemap(singleRemap || LoomGradleExtension.get(getProject()).isRootProject());
if (singleRemap) {
- jarRemapper.remap();
+ jarRemapper.remap(getProject());
}
+
+ convertAwToAt();
}
public void scheduleRemap(boolean isMainRemapTask) throws Throwable {
@@ -132,24 +161,27 @@ public class RemapJarTask extends Jar {
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
- String fromM = MappingsNamespace.NAMED.toString();
- String toM = MappingsNamespace.INTERMEDIARY.toString();
+ String fromM = this.fromM.get();
+ String toM = this.toM.get();
if (isMainRemapTask) {
jarRemapper.addToClasspath(getRemapClasspath());
- jarRemapper.addMappings(TinyRemapperHelper.create(mappingsProvider.getMappings(), fromM, toM, false));
+ jarRemapper.addMappings(TinyRemapperHelper.create((fromM.equals("srg") || toM.equals("srg")) && extension.shouldGenerateSrgTiny() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(), fromM, toM, false));
}
for (File mixinMapFile : extension.getAllMixinMappings()) {
if (mixinMapFile.exists()) {
- jarRemapper.addMappings(TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, toM));
+ IMappingProvider provider = TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, "intermediary");
+ jarRemapper.addMappings(!toM.equals("intermediary") ? remapToSrg(extension, provider, "intermediary", toM) : provider);
}
}
// Add remap options to the jar remapper
jarRemapper.addOptions(this.remapOptions);
+ project.getLogger().info(":scheduling remap " + input.getFileName() + " from " + fromM + " to " + toM);
+
NestedJarProvider nestedJarProvider = getNestedJarProvider();
nestedJarProvider.prepare(getProject());
@@ -166,7 +198,7 @@ public class RemapJarTask extends Jar {
}
AccessWidenerFile awFile = AccessWidenerFile.fromModJar(remapData.input);
- Preconditions.checkNotNull(awFile, "Failed to find accessWidener in fabric.mod.json: " + remapData.input);
+ Preconditions.checkNotNull(awFile, "Failed to find accessWidener in fabric.mod.json / architectury.common.json: " + remapData.input);
return Pair.of(awFile.name(), data);
}
@@ -182,6 +214,8 @@ public class RemapJarTask extends Jar {
if (MixinRefmapHelper.addRefmapName(project, output)) {
project.getLogger().debug("Transformed mixin reference maps in output JAR!");
}
+ } else if (extension.isForge()) {
+ throw new RuntimeException("Forge must have useLegacyMixinAp enabled");
}
if (getAddNestedDependencies().getOrElse(false)) {
@@ -196,23 +230,25 @@ public class RemapJarTask extends Jar {
}
}
- // Add data to the manifest
- try {
- int count = ZipUtils.transform(data.output, Map.of(MANIFEST_PATH, bytes -> {
- var manifest = new Manifest(new ByteArrayInputStream(bytes));
- var manifestConfiguration = new JarManifestConfiguration(project);
+ if (!extension.isForge()) {
+ // Add data to the manifest
+ try {
+ int count = ZipUtils.transform(data.output, Map.of(MANIFEST_PATH, bytes -> {
+ var manifest = new Manifest(new ByteArrayInputStream(bytes));
+ var manifestConfiguration = new JarManifestConfiguration(project);
- manifestConfiguration.configure(manifest);
- manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", toM);
+ manifestConfiguration.configure(manifest);
+ manifest.getMainAttributes().putValue("Fabric-Mapping-Namespace", toM);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- manifest.write(out);
- return out.toByteArray();
- }));
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ manifest.write(out);
+ return out.toByteArray();
+ }));
- Preconditions.checkState(count > 0, "Did not transform any jar manifest");
- } catch (IOException e) {
- throw new UncheckedIOException("Failed to transform jar manifest", e);
+ Preconditions.checkState(count > 0, "Did not transform any jar manifest");
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to transform jar manifest", e);
+ }
}
if (isReproducibleFileOrder() || !isPreserveFileTimestamps()) {
@@ -226,6 +262,10 @@ public class RemapJarTask extends Jar {
}
private NestedJarProvider getNestedJarProvider() {
+ if (!LoomGradleExtension.get(getProject()).supportsInclude()) {
+ return EmptyNestedJarProvider.INSTANCE;
+ }
+
Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE);
if (!addDefaultNestedDependencies.getOrElse(true)) {
@@ -244,6 +284,57 @@ public class RemapJarTask extends Jar {
);
}
+ private IMappingProvider remapToSrg(LoomGradleExtension extension, IMappingProvider parent, String from, String to) throws IOException {
+ MappingTree mappings = (from.equals("srg") || to.equals("srg")) && extension.shouldGenerateSrgTiny() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings();
+
+ return sink -> {
+ parent.load(new IMappingProvider.MappingAcceptor() {
+ @Override
+ public void acceptClass(String srcName, String dstName) {
+ String srgName = mappings.getClasses()
+ .stream()
+ .filter(it -> Objects.equals(it.getName(from), dstName))
+ .findFirst()
+ .map(it -> it.getName(to))
+ .orElse(dstName);
+ sink.acceptClass(srcName, srgName);
+ }
+
+ @Override
+ public void acceptMethod(IMappingProvider.Member method, String dstName) {
+ String srgName = mappings.getClasses()
+ .stream()
+ .flatMap(it -> it.getMethods().stream())
+ .filter(it -> Objects.equals(it.getName(from), dstName))
+ .findFirst()
+ .map(it -> it.getName(to))
+ .orElse(dstName);
+ sink.acceptMethod(method, srgName);
+ }
+
+ @Override
+ public void acceptField(IMappingProvider.Member field, String dstName) {
+ String srgName = mappings.getClasses()
+ .stream()
+ .flatMap(it -> it.getFields().stream())
+ .filter(it -> Objects.equals(it.getName(from), dstName))
+ .findFirst()
+ .map(it -> it.getName(to))
+ .orElse(dstName);
+ sink.acceptField(field, srgName);
+ }
+
+ @Override
+ public void acceptMethodArg(IMappingProvider.Member method, int lvIndex, String dstName) {
+ }
+
+ @Override
+ public void acceptMethodVar(IMappingProvider.Member method, int lvIndex, int startOpIdx, int asmIndex, String dstName) {
+ }
+ });
+ };
+ }
+
private Path[] getRemapClasspath() {
FileCollection files = this.classpath;
@@ -257,6 +348,56 @@ public class RemapJarTask extends Jar {
.toArray(Path[]::new);
}
+ private void convertAwToAt() throws IOException {
+ if (!this.atAccessWideners.isPresent()) {
+ return;
+ }
+
+ Set<String> atAccessWideners = this.atAccessWideners.get();
+
+ if (atAccessWideners.isEmpty()) {
+ return;
+ }
+
+ AccessTransformSet at = AccessTransformSet.create();
+ File jar = getArchiveFile().get().getAsFile();
+
+ try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(jar, false)) {
+ FileSystem fs = fileSystem.get();
+ Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH);
+
+ if (Files.exists(atPath)) {
+ throw new FileAlreadyExistsException("Jar " + jar + " already contains an access transformer - cannot convert AWs!");
+ }
+
+ for (String aw : atAccessWideners) {
+ Path awPath = fs.getPath(aw);
+
+ if (Files.notExists(awPath)) {
+ throw new NoSuchFileException("Could not find AW '" + aw + "' to convert into AT!");
+ }
+
+ try (BufferedReader reader = Files.newBufferedReader(awPath, StandardCharsets.UTF_8)) {
+ at.merge(Aw2At.toAccessTransformSet(reader));
+ }
+
+ Files.delete(awPath);
+ }
+
+ LoomGradleExtension extension = LoomGradleExtension.get(getProject());
+ MappingTree mappings = (fromM.get().equals("srg") || toM.get().equals("srg")) && extension.shouldGenerateSrgTiny() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings();
+
+ try (TinyMappingsReader reader = new TinyMappingsReader(mappings, fromM.get(), toM.get())) {
+ MappingSet mappingSet = reader.read();
+ at = at.remap(mappingSet);
+ }
+
+ try (Writer writer = new LfWriter(Files.newBufferedWriter(atPath))) {
+ AccessTransformFormats.FML.write(writer, at);
+ }
+ }
+ }
+
@InputFile
public RegularFileProperty getInput() {
return input;
@@ -277,6 +418,19 @@ public class RemapJarTask extends Jar {
return remapAccessWidener;
}
+ /**
+ * Gets the jar paths to the access wideners that will be converted to ATs for Forge runtime.
+ * If you specify multiple files, they will be merged into one.
+ *
+ * <p>The specified files will be converted and removed from the final jar.
+ *
+ * @return the property containing access widener paths in the final jar
+ */
+ @Input
+ public SetProperty<String> getAtAccessWideners() {
+ return atAccessWideners;
+ }
+
public void remapOptions(Action<TinyRemapper.Builder> action) {
this.remapOptions.add(action);
}
@@ -299,4 +453,14 @@ public class RemapJarTask extends Jar {
return this;
}
+
+ @Input
+ public Property<String> getFromM() {
+ return fromM;
+ }
+
+ @Input
+ public Property<String> getToM() {
+ return toM;
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java
index 26fd6a37..97556c13 100644
--- a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java
+++ b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java
@@ -38,19 +38,26 @@ import net.fabricmc.loom.util.SourceRemapper;
public class RemapSourcesJarTask extends AbstractLoomTask {
private final RegularFileProperty input = getProject().getObjects().fileProperty();
private final RegularFileProperty output = getProject().getObjects().fileProperty().convention(input);
- private final Property<String> targetNamespace = getProject().getObjects().property(String.class).convention(MappingsNamespace.INTERMEDIARY.toString());
+ private final Property<String> sourceNamespace;
+ private final Property<String> targetNamespace;
private SourceRemapper sourceRemapper = null;
private final Property<Boolean> preserveFileTimestamps = getProject().getObjects().property(Boolean.class).convention(true);
private final Property<Boolean> reproducibleFileOrder = getProject().getObjects().property(Boolean.class).convention(false);
public RemapSourcesJarTask() {
+ this.sourceNamespace = getProject().getObjects().property(String.class).convention("named");
+ this.targetNamespace = getProject().getObjects().property(String.class).convention(SourceRemapper.intermediary(getProject()));
}
@TaskAction
public void remap() throws Exception {
if (sourceRemapper == null) {
- String direction = targetNamespace.get();
- SourceRemapper.remapSources(getProject(), input.get().getAsFile(), output.get().getAsFile(), direction.equals(MappingsNamespace.NAMED.toString()), reproducibleFileOrder.get(), preserveFileTimestamps.get());
+ if (sourceNamespace.get().equals(targetNamespace.get())) {
+ SourceRemapper.remapSources(getProject(), getInput().get().getAsFile(), getOutput().get().getAsFile(),
+ targetNamespace.get().equals(MappingsNamespace.NAMED.toString()) ? SourceRemapper.intermediary(getProject()) : "named", targetNamespace.get(), reproducibleFileOrder.get(), preserveFileTimestamps.get());
+ } else {
+ SourceRemapper.remapSources(getProject(), getInput().get().getAsFile(), getOutput().get().getAsFile(), sourceNamespace.get(), targetNamespace.get(), reproducibleFileOrder.get(), preserveFileTimestamps.get());
+ }
} else {
sourceRemapper.scheduleRemapSources(input.get().getAsFile(), output.get().getAsFile(), reproducibleFileOrder.get(), preserveFileTimestamps.get());
}
@@ -77,6 +84,11 @@ public class RemapSourcesJarTask extends AbstractLoomTask {
}
@Input
+ public Property<String> getSourceNamespace() {
+ return sourceNamespace;
+ }
+
+ @Input
public Property<String> getTargetNamespace() {
return targetNamespace;
}
diff --git a/src/main/java/net/fabricmc/loom/task/RunDataTask.java b/src/main/java/net/fabricmc/loom/task/RunDataTask.java
new file mode 100644
index 00000000..37b3430b
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/task/RunDataTask.java
@@ -0,0 +1,38 @@
+/*
+ * 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.task;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.configuration.ide.RunConfig;
+
+@Deprecated // Replaced by RunGameTask
+public class RunDataTask extends AbstractRunTask {
+ public RunDataTask() {
+ super(project -> {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+ return RunConfig.runConfig(project, extension.getRunConfigs().getByName("data"));
+ });
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java
index 51f9d97c..7fc9d227 100644
--- a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java
+++ b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java
@@ -32,6 +32,8 @@ import java.nio.file.Files;
import javax.inject.Inject;
+import dev.architectury.tinyremapper.TinyRemapper;
+import dev.architectury.tinyremapper.api.TrEnvironment;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFile;
@@ -42,8 +44,6 @@ import net.fabricmc.accesswidener.AccessWidenerFormatException;
import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerVisitor;
import net.fabricmc.loom.LoomGradleExtension;
-import net.fabricmc.tinyremapper.TinyRemapper;
-import net.fabricmc.tinyremapper.api.TrEnvironment;
public abstract class ValidateAccessWidenerTask extends DefaultTask {
@SkipWhenEmpty
diff --git a/src/main/java/net/fabricmc/loom/util/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java
index e3607d4c..ba9cd2e6 100644
--- a/src/main/java/net/fabricmc/loom/util/Checksum.java
+++ b/src/main/java/net/fabricmc/loom/util/Checksum.java
@@ -27,6 +27,7 @@ package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
@@ -44,8 +45,9 @@ public class Checksum {
try {
HashCode hash = Files.asByteSource(file).hash(Hashing.sha1());
- log.debug("Checksum check: '" + hash.toString() + "' == '" + checksum + "'?");
- return hash.toString().equals(checksum);
+ String hashString = hash.toString();
+ log.debug("Checksum check: '" + hashString + "' == '" + checksum + "'?");
+ return hashString.equals(checksum);
} catch (IOException e) {
e.printStackTrace();
}
@@ -70,4 +72,9 @@ public class Checksum {
throw new UncheckedIOException("Failed to get file hash of " + file, e);
}
}
+
+ public static byte[] sha256(String string) {
+ HashCode hash = Hashing.sha256().hashString(string, StandardCharsets.UTF_8);
+ return hash.asBytes();
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java
index dae98a87..8a68b0fb 100644
--- a/src/main/java/net/fabricmc/loom/util/Constants.java
+++ b/src/main/java/net/fabricmc/loom/util/Constants.java
@@ -34,6 +34,7 @@ import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry.PublishingMode;
public class Constants {
+ public static final String PLUGIN_ID = "dev.architectury.loom";
public static final String LIBRARIES_BASE = "https://libraries.minecraft.net/";
public static final String RESOURCES_BASE = "https://resources.download.minecraft.net/";
public static final String VERSION_MANIFESTS = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json";
@@ -72,6 +73,30 @@ public class Constants {
public static final String MAPPINGS_FINAL = "mappingsFinal";
public static final String LOADER_DEPENDENCIES = "loaderLibraries";
public static final String LOOM_DEVELOPMENT_DEPENDENCIES = "loomDevelopmentDependencies";
+ public static final String SRG = "srg";
+ public static final String MCP_CONFIG = "mcp";
+ public static final String FORGE = "forge";
+ public static final String FORGE_USERDEV = "forgeUserdev";
+ public static final String FORGE_INSTALLER = "forgeInstaller";
+ public static final String FORGE_UNIVERSAL = "forgeUniversal";
+ /**
+ * Forge's own dependencies. Not intended to be used by users,
+ * {@link #FORGE_RUNTIME_LIBRARY forgeRuntimeLibrary} is for that instead.
+ */
+ public static final String FORGE_DEPENDENCIES = "forgeDependencies";
+ public static final String FORGE_NAMED = "forgeNamed";
+ /**
+ * "Extra" runtime dependencies on Forge. Contains the Minecraft resources
+ * and {@linkplain Dependencies#FORGE_RUNTIME the Architectury Loom runtime}.
+ */
+ public static final String FORGE_EXTRA = "forgeExtra";
+ /**
+ * The configuration used to create the Forge runtime classpath file list.
+ * Users can also directly add files to this config.
+ *
+ * @see net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider
+ */
+ public static final String FORGE_RUNTIME_LIBRARY = "forgeRuntimeLibrary";
public static final String MAPPING_CONSTANTS = "mappingsConstants";
public static final String UNPICK_CLASSPATH = "unpick";
/**
@@ -94,6 +119,9 @@ public class Constants {
public static final String DEV_LAUNCH_INJECTOR = "net.fabricmc:dev-launch-injector:";
public static final String TERMINAL_CONSOLE_APPENDER = "net.minecrell:terminalconsoleappender:";
public static final String JETBRAINS_ANNOTATIONS = "org.jetbrains:annotations:";
+ public static final String JAVAX_ANNOTATIONS = "com.google.code.findbugs:jsr305:"; // I hate that I have to add these.
+ public static final String FORGE_RUNTIME = "dev.architectury:architectury-loom-runtime:";
+ public static final String ACCESS_TRANSFORMERS = "net.minecraftforge:accesstransformers:";
private Dependencies() {
}
@@ -106,6 +134,10 @@ public class Constants {
public static final String DEV_LAUNCH_INJECTOR = "0.2.1+build.8";
public static final String TERMINAL_CONSOLE_APPENDER = "1.2.0";
public static final String JETBRAINS_ANNOTATIONS = "22.0.0";
+ public static final String JAVAX_ANNOTATIONS = "3.0.2";
+ public static final String FORGE_RUNTIME = "1.1.3";
+ public static final String ACCESS_TRANSFORMERS = "3.0.1";
+ public static final String ACCESS_TRANSFORMERS_NEW = "8.0.5";
private Versions() {
}
@@ -131,10 +163,18 @@ public class Constants {
}
public static final class TaskGroup {
- public static final String FABRIC = "fabric";
+ public static final String FABRIC = "loom";
public static final String IDE = "ide";
private TaskGroup() {
}
}
+
+ public static final class Forge {
+ public static final String LAUNCH_TESTING = "net.minecraftforge.userdev.LaunchTesting";
+ public static final String ACCESS_TRANSFORMER_PATH = "META-INF/accesstransformer.cfg";
+
+ private Forge() {
+ }
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java b/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java
new file mode 100644
index 00000000..329d88bf
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java
@@ -0,0 +1,97 @@
+/*
+ * 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.util;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.file.FileCollection;
+
+/**
+ * Simplified dependency downloading.
+ *
+ * @author Juuz
+ */
+public final class DependencyDownloader {
+ /**
+ * Resolves a dependency as well as its transitive dependencies into a {@link FileCollection}.
+ *
+ * @param project the project needing these files
+ * @param dependencyNotation the dependency notation
+ * @return the resolved files
+ */
+ public static FileCollection download(Project project, String dependencyNotation) {
+ return download(project, dependencyNotation, true, false);
+ }
+
+ public static FileCollection download(Project project, String dependencyNotation, boolean transitive, boolean resolve) {
+ Dependency dependency = project.getDependencies().create(dependencyNotation);
+
+ if (dependency instanceof ModuleDependency) {
+ ((ModuleDependency) dependency).setTransitive(transitive);
+ }
+
+ Configuration config = project.getConfigurations().detachedConfiguration(dependency);
+ config.setTransitive(transitive);
+ FileCollection files = config.fileCollection(dep -> true);
+
+ if (resolve) {
+ files = project.files(files.getFiles());
+ }
+
+ return files;
+ }
+
+ private static Set<File> resolve(Configuration configuration, boolean transitive) {
+ Configuration copy = configuration.copy();
+ copy.setTransitive(transitive);
+ Set<File> files = new LinkedHashSet<>(copy.resolve());
+
+ for (Configuration extendsForm : configuration.getExtendsFrom()) {
+ files.addAll(resolve(extendsForm, transitive));
+ }
+
+ return files;
+ }
+
+ /**
+ * Resolves a configuration and its superconfigurations.
+ *
+ * <p>Note that unlike resolving a {@linkplain Configuration#copyRecursive() recursive copy} of the configuration,
+ * this method overrides the transitivity of all superconfigurations as well.
+ *
+ * @param configuration the configuration to resolve
+ * @param transitive true if transitive dependencies should be included, false otherwise
+ * @return a mutable set containing the resolved files of the configuration
+ */
+ public static Set<File> resolveFiles(Configuration configuration, boolean transitive) {
+ return resolve(configuration, transitive);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
index fcc79aa7..56ccf8d3 100644
--- a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
+++ b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
@@ -48,8 +48,8 @@ public class DownloadUtil {
* @param logger The logger to print everything to, typically from {@link Project#getLogger()}
* @throws IOException If an exception occurs during the process
*/
- public static void downloadIfChanged(URL from, File to, Logger logger) throws IOException {
- downloadIfChanged(from, to, logger, false);
+ public static boolean downloadIfChanged(URL from, File to, Logger logger) throws IOException {
+ return downloadIfChanged(from, to, logger, false);
}
/**
@@ -61,7 +61,7 @@ public class DownloadUtil {
* @param quiet Whether to only print warnings (when <code>true</code>) or everything
* @throws IOException If an exception occurs during the process
*/
- public static void downloadIfChanged(URL from, File to, Logger logger, boolean quiet) throws IOException {
+ public static boolean downloadIfChanged(URL from, File to, Logger logger, boolean quiet) throws IOException {
HttpURLConnection connection = (HttpURLConnection) from.openConnection();
if (LoomGradlePlugin.refreshDeps) {
@@ -102,7 +102,7 @@ public class DownloadUtil {
logger.info("'{}' Not Modified, skipping.", to);
}
- return; //What we've got is already fine
+ return false; //What we've got is already fine
}
long contentLength = connection.getContentLengthLong();
@@ -140,6 +140,8 @@ public class DownloadUtil {
saveETag(to, eTag, logger);
}
+
+ return true;
}
/**
@@ -228,5 +230,7 @@ public class DownloadUtil {
if (etagFile.exists()) {
etagFile.delete();
}
+
+ HashedDownloadUtil.delete(file);
}
}
diff --git a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java
index 377bb8cb..05f0642f 100644
--- a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java
+++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java
@@ -78,6 +78,8 @@ public final class FileSystemUtil {
return new Delegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true);
} catch (FileSystemAlreadyExistsException e) {
return new Delegate(FileSystems.getFileSystem(jarUri), false);
+ } catch (IOException e) {
+ throw new IOException("Could not create JAR file system for " + uri + " (create: " + create + ")", e);
}
}
}
diff --git a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
index 3ca404e2..a9b77ab4 100644
--- a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
+++ b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
@@ -43,20 +43,48 @@ import org.gradle.api.logging.Logger;
import net.fabricmc.loom.LoomGradlePlugin;
public class HashedDownloadUtil {
+ public static boolean requiresDownload(File to, String expectedHash, Logger logger) {
+ if (LoomGradlePlugin.refreshDeps) {
+ return true;
+ }
+
+ if (to.exists()) {
+ String sha1 = getSha1(to, logger);
+
+ // The hash in the sha1 file matches
+ return !expectedHash.equals(sha1);
+ }
+
+ return true;
+ }
+
public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet) throws IOException {
- downloadIfInvalid(from, to, expectedHash, logger, quiet, () -> { });
+ downloadIfInvalid(from, to, expectedHash, logger, quiet, true);
+ }
+
+ public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, boolean strict) throws IOException {
+ downloadIfInvalid(from, to, expectedHash, logger, quiet, strict, () -> { });
}
- public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, Runnable startDownload) throws IOException {
+ public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, boolean strict, Runnable startDownload) throws IOException {
if (LoomGradlePlugin.refreshDeps) {
delete(to);
}
- String sha1 = getSha1(to, logger);
-
- if (expectedHash.equals(sha1)) {
- // The hash in the sha1 file matches
- return;
+ if (to.exists()) {
+ if (strict) {
+ if (Checksum.equals(to, expectedHash)) {
+ // The hash matches the target file
+ return;
+ }
+ } else {
+ String sha1 = getSha1(to, logger);
+
+ if (expectedHash.equals(sha1)) {
+ // The hash in the sha1 file matches
+ return;
+ }
+ }
}
startDownload.run();
diff --git a/src/main/java/net/fabricmc/loom/util/LfWriter.java b/src/main/java/net/fabricmc/loom/util/LfWriter.java
new file mode 100644
index 00000000..cbd6540f
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/LfWriter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.util;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * A {@link BufferedWriter} that writes {@code \n} (LF) instead of {@link System#lineSeparator()}.
+ */
+public class LfWriter extends BufferedWriter {
+ public LfWriter(Writer out) {
+ super(out);
+ }
+
+ @Override
+ public void newLine() throws IOException {
+ write('\n');
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/LoggerFilter.java b/src/main/java/net/fabricmc/loom/util/LoggerFilter.java
new file mode 100644
index 00000000..1f9f9b96
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/LoggerFilter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.util;
+
+import java.io.PrintStream;
+
+import org.jetbrains.annotations.NotNull;
+
+public class LoggerFilter {
+ public static void replaceSystemOut() {
+ try {
+ PrintStream previous = System.out;
+ System.setOut(new PrintStream(previous) {
+ @Override
+ public PrintStream printf(@NotNull String format, Object... args) {
+ if (format.equals("unknown invokedynamic bsm: %s/%s%s (tag=%d iif=%b)%n")) {
+ return this;
+ }
+
+ return super.printf(format, args);
+ }
+ });
+ } catch (SecurityException ignored) {
+ // Failed to replace logger filter, just ignore
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java b/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java
new file mode 100644
index 00000000..9ddb07ef
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+
+import dev.architectury.tinyremapper.IMappingProvider;
+import dev.architectury.tinyremapper.TinyRemapper;
+
+import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor;
+import net.fabricmc.mappingio.format.Tiny2Writer;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+public class MappingsProviderVerbose {
+ public static void saveFile(TinyRemapper providers) throws IOException {
+ try {
+ Field field = TinyRemapper.class.getDeclaredField("mappingProviders");
+ field.setAccessible(true);
+ Set<IMappingProvider> mappingProviders = (Set<IMappingProvider>) field.get(providers);
+ saveFile(mappingProviders);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void saveFile(Iterable<IMappingProvider> providers) throws IOException {
+ MemoryMappingTree tree = new MemoryMappingTree();
+ tree.setSrcNamespace("from");
+ tree.setDstNamespaces(new ArrayList<>(Collections.singletonList("to")));
+ RegularAsFlatMappingVisitor flatVisitor = new RegularAsFlatMappingVisitor(tree);
+
+ for (IMappingProvider provider : providers) {
+ provider.load(new IMappingProvider.MappingAcceptor() {
+ @Override
+ public void acceptClass(String from, String to) {
+ try {
+ flatVisitor.visitClass(from, to);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void acceptMethod(IMappingProvider.Member from, String to) {
+ try {
+ flatVisitor.visitMethod(from.owner, from.name, from.desc, to);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void acceptMethodArg(IMappingProvider.Member from, int lvIndex, String to) {
+ try {
+ flatVisitor.visitMethodArg(from.owner, from.name, from.desc, lvIndex, lvIndex, "", to);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void acceptMethodVar(IMappingProvider.Member from, int i, int i1, int i2, String s) {
+ // NO-OP
+ }
+
+ @Override
+ public void acceptField(IMappingProvider.Member from, String to) {
+ try {
+ flatVisitor.visitField(from.owner, from.name, from.desc, to);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ });
+ }
+
+ Path check = Files.createTempFile("CHECK", null);
+ StringWriter stringWriter = new StringWriter();
+ Tiny2Writer tiny2Writer = new Tiny2Writer(stringWriter, false);
+ tree.accept(tiny2Writer);
+ Files.writeString(check, stringWriter.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ System.out.println("Saved debug check mappings to " + check);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/ModPlatform.java b/src/main/java/net/fabricmc/loom/util/ModPlatform.java
new file mode 100644
index 00000000..0f7ce897
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/ModPlatform.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util;
+
+import java.util.Locale;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.api.LoomGradleExtensionAPI;
+
+public enum ModPlatform {
+ FABRIC,
+ FORGE;
+
+ public static void assertPlatform(Project project, ModPlatform platform) {
+ assertPlatform(LoomGradleExtension.get(project), platform);
+ }
+
+ public static void assertPlatform(LoomGradleExtensionAPI extension, ModPlatform platform) {
+ if (extension.getPlatform().get() != platform) {
+ String msg = "Loom is not running on %s.%nYou can switch to it by adding 'loom.platform = %s' to your gradle.properties";
+ String name = platform.name().toLowerCase(Locale.ROOT);
+ throw new GradleException(String.format(msg, name, name));
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/PropertyUtil.java b/src/main/java/net/fabricmc/loom/util/PropertyUtil.java
new file mode 100644
index 00000000..98e85b68
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/PropertyUtil.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util;
+
+import org.gradle.api.provider.HasConfigurableValue;
+import org.gradle.api.provider.Provider;
+
+/**
+ * Working with properties.
+ */
+public final class PropertyUtil {
+ /**
+ * Gets a property and finalizes its value.
+ *
+ * @param property the property
+ * @param <T> the value type
+ * @return the current property value
+ */
+ public static <T, P extends Provider<T> & HasConfigurableValue> T getAndFinalize(P property) {
+ property.finalizeValue();
+ return property.get();
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
index f37211be..7873c78f 100644
--- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
+++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
@@ -39,7 +39,6 @@ import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
-import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
@@ -48,18 +47,29 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class SourceRemapper {
private final Project project;
- private final boolean toNamed;
+ private String from;
+ private String to;
private final List<Consumer<ProgressLoggerHelper>> remapTasks = new ArrayList<>();
private Mercury mercury;
- public SourceRemapper(Project project, boolean toNamed) {
+ public SourceRemapper(Project project, boolean named) {
+ this(project, named ? intermediary(project) : "named", !named ? intermediary(project) : "named");
+ }
+
+ public static String intermediary(Project project) {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+ return extension.isForge() ? "srg" : "intermediary";
+ }
+
+ public SourceRemapper(Project project, String from, String to) {
this.project = project;
- this.toNamed = toNamed;
+ this.from = from;
+ this.to = to;
}
- public static void remapSources(Project project, File input, File output, boolean named, boolean reproducibleFileOrder, boolean preserveFileTimestamps) {
- SourceRemapper sourceRemapper = new SourceRemapper(project, named);
+ public static void remapSources(Project project, File input, File output, String from, String to, boolean reproducibleFileOrder, boolean preserveFileTimestamps) {
+ SourceRemapper sourceRemapper = new SourceRemapper(project, from, to);
sourceRemapper.scheduleRemapSources(input, output, reproducibleFileOrder, preserveFileTimestamps);
sourceRemapper.remapAll();
}
@@ -161,18 +171,27 @@ public class SourceRemapper {
LoomGradleExtension extension = LoomGradleExtension.get(project);
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
- MappingSet mappings = extension.getOrCreateSrcMappingCache(toNamed ? 1 : 0, () -> {
+ String intermediary = extension.isForge() ? "srg" : "intermediary";
+ int id = -1;
+
+ if (from.equals(intermediary) && to.equals("named")) {
+ id = 1;
+ } else if (to.equals(intermediary) && from.equals("named")) {
+ id = 0;
+ }
+
+ MappingSet mappings = extension.getOrCreateSrcMappingCache(id, () -> {
try {
- MemoryMappingTree m = mappingsProvider.getMappings();
- project.getLogger().info(":loading " + (toNamed ? "intermediary -> named" : "named -> intermediary") + " source mappings");
- return new TinyMappingsReader(m, toNamed ? MappingsNamespace.INTERMEDIARY.toString() : MappingsNamespace.NAMED.toString(), toNamed ? MappingsNamespace.NAMED.toString() : MappingsNamespace.INTERMEDIARY.toString()).read();
+ MemoryMappingTree m = (from.equals("srg") || to.equals("srg")) && extension.shouldGenerateSrgTiny() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings();
+ project.getLogger().info(":loading " + from + " -> " + to + " source mappings");
+ return new TinyMappingsReader(m, from, to).read();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
- Mercury mercury = extension.getOrCreateSrcMercuryCache(toNamed ? 1 : 0, () -> {
- Mercury m = createMercuryWithClassPath(project, toNamed);
+ Mercury mercury = extension.getOrCreateSrcMercuryCache(id, () -> {
+ Mercury m = createMercuryWithClassPath(project, to.equals("named"));
for (File file : extension.getUnmappedModCollection()) {
Path path = file.toPath();
@@ -185,6 +204,16 @@ public class SourceRemapper {
m.getClassPath().add(extension.getMinecraftMappedProvider().getMappedJar().toPath());
m.getClassPath().add(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath());
+ if (extension.isForge()) {
+ m.getClassPath().add(extension.getMinecraftMappedProvider().getSrgJar().toPath());
+
+ if (extension.isForgeAndNotOfficial()) {
+ m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeMappedJar().toPath());
+ m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeIntermediaryJar().toPath());
+ m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeSrgJar().toPath());
+ }
+ }
+
Set<File> files = project.getConfigurations()
.detachedConfiguration(project.getDependencies().create(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS))
.resolve();
diff --git a/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java
new file mode 100644
index 00000000..d6d06f79
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java
@@ -0,0 +1,192 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import com.google.common.base.Stopwatch;
+
+public class ThreadingUtils {
+ public static <T> void run(T[] values, UnsafeConsumer<T> action) {
+ run(Arrays.stream(values)
+ .<UnsafeRunnable>map(t -> () -> action.accept(t))
+ .collect(Collectors.toList()));
+ }
+
+ public static <T> void run(Collection<T> values, UnsafeConsumer<T> action) {
+ run(values.stream()
+ .<UnsafeRunnable>map(t -> () -> action.accept(t))
+ .collect(Collectors.toList()));
+ }
+
+ public static void run(UnsafeRunnable... jobs) {
+ run(Arrays.asList(jobs));
+ }
+
+ public static void run(Collection<UnsafeRunnable> jobs) {
+ try {
+ ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Math.min(jobs.size(), Runtime.getRuntime().availableProcessors())));
+ List<Future<?>> futures = new LinkedList<>();
+
+ for (UnsafeRunnable runnable : jobs) {
+ futures.add(service.submit(() -> {
+ try {
+ runnable.run();
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }));
+ }
+
+ for (Future<?> future : futures) {
+ future.get();
+ }
+
+ service.shutdownNow();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static <T, R> List<R> get(Collection<T> values, Function<T, R> action) {
+ return get(values.stream()
+ .<UnsafeCallable<R>>map(t -> () -> action.apply(t))
+ .collect(Collectors.toList()));
+ }
+
+ @SafeVarargs
+ public static <T> List<T> get(UnsafeCallable<T>... jobs) {
+ return get(Arrays.asList(jobs));
+ }
+
+ public static <T> List<T> get(Collection<UnsafeCallable<T>> jobs) {
+ try {
+ ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Math.min(jobs.size(), Runtime.getRuntime().availableProcessors())));
+ List<Future<T>> futures = new LinkedList<>();
+ List<T> result = new ArrayList<>();
+
+ for (UnsafeCallable<T> runnable : jobs) {
+ futures.add(service.submit(() -> {
+ try {
+ return runnable.call();
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }));
+ }
+
+ for (Future<T> future : futures) {
+ result.add(future.get());
+ }
+
+ service.shutdownNow();
+ return result;
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public interface UnsafeRunnable {
+ void run() throws Throwable;
+ }
+
+ public interface UnsafeCallable<T> {
+ T call() throws Throwable;
+ }
+
+ public interface UnsafeConsumer<T> {
+ void accept(T value) throws Throwable;
+ }
+
+ public static TaskCompleter taskCompleter() {
+ return new TaskCompleter();
+ }
+
+ public static class TaskCompleter implements Function<Throwable, Void> {
+ Stopwatch stopwatch = Stopwatch.createUnstarted();
+ List<CompletableFuture<?>> tasks = new ArrayList<>();
+ ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors()));
+ List<UnsafeConsumer<Stopwatch>> completionListener = new ArrayList<>();
+
+ public TaskCompleter add(UnsafeRunnable job) {
+ if (!stopwatch.isRunning()) {
+ stopwatch.start();
+ }
+
+ tasks.add(CompletableFuture.runAsync(() -> {
+ try {
+ job.run();
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }, service).exceptionally(this));
+
+ return this;
+ }
+
+ public TaskCompleter onComplete(UnsafeConsumer<Stopwatch> consumer) {
+ completionListener.add(consumer);
+ return this;
+ }
+
+ public void complete() {
+ try {
+ CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0])).exceptionally(this).get();
+ service.shutdownNow();
+
+ if (stopwatch.isRunning()) {
+ stopwatch.stop();
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ for (UnsafeConsumer<Stopwatch> consumer : completionListener) {
+ consumer.accept(stopwatch);
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public Void apply(Throwable throwable) {
+ throwable.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java
index d534f972..1dc42878 100644
--- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java
+++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java
@@ -27,25 +27,31 @@ package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dev.architectury.tinyremapper.IMappingProvider;
+import dev.architectury.tinyremapper.TinyRemapper;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.commons.lang3.tuple.Triple;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
-import net.fabricmc.tinyremapper.IMappingProvider;
-import net.fabricmc.tinyremapper.TinyRemapper;
/**
* Contains shortcuts to create tiny remappers using the mappings accessibly to the project.
*/
public final class TinyRemapperHelper {
- 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")
@@ -65,31 +71,62 @@ public final class TinyRemapperHelper {
public static TinyRemapper getTinyRemapper(Project project, String fromM, String toM, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
- MemoryMappingTree mappingTree = extension.getMappingsProvider().getMappings();
- if (fixRecords && !mappingTree.getSrcNamespace().equals(fromM)) {
- throw new IllegalStateException("Mappings src namespace must match remap src namespace");
- }
+ TinyRemapper remapper = _getTinyRemapper(project, fixRecords, builderConsumer).getLeft();
+ remapper.replaceMappings(ImmutableSet.of(
+ TinyRemapperHelper.create((fromM.equals("srg") || toM.equals("srg")) && extension.isForge() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings(), fromM, toM, true),
+ out -> TinyRemapperHelper.JSR_TO_JETBRAINS.forEach(out::acceptClass)
+ ));
+ return remapper;
+ }
- int intermediaryNsId = mappingTree.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString());
+ public static Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> _getTinyRemapper(Project project, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+ Mutable<MemoryMappingTree> mappings = new MutableObject<>();
+ List<TinyRemapper.ApplyVisitorProvider> postApply = new ArrayList<>();
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
- .withMappings(create(mappingTree, fromM, toM, true))
- .withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass))
.renameInvalidLocals(true)
- .rebuildSourceFilenames(true)
- .invalidLvNamePattern(MC_LV_PATTERN)
- .inferNameFromSameLvIndex(true)
- .extraPreApplyVisitor((cls, next) -> {
- if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName())) {
- return new RecordComponentFixVisitor(next, mappingTree, intermediaryNsId);
- }
+ .logUnknownInvokeDynamic(false)
+ .ignoreConflicts(extension.isForge())
+ .cacheMappings(true)
+ .threads(Runtime.getRuntime().availableProcessors())
+ .logger(project.getLogger()::lifecycle)
+ .rebuildSourceFilenames(true);
+
+ if (extension.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 next;
- });
+ builder.invalidLvNamePattern(MC_LV_PATTERN);
+ builder.inferNameFromSameLvIndex(true);
+ builder.extraPreApplyVisitor((cls, next) -> {
+ if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName()) && mappings.getValue() != null) {
+ return new RecordComponentFixVisitor(next, mappings.getValue(), mappings.getValue().getNamespaceId(MappingsNamespace.INTERMEDIARY.toString()));
+ }
+
+ return next;
+ });
+ builder.extraPostApplyVisitor((trClass, classVisitor) -> {
+ for (TinyRemapper.ApplyVisitorProvider provider : postApply) {
+ classVisitor = provider.insertApplyVisitor(trClass, classVisitor);
+ }
+
+ return classVisitor;
+ });
builderConsumer.accept(builder);
- return builder.build();
+ return Triple.of(builder.build(), mappings, postApply);
+ }
+
+ public static Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> getTinyRemapper(Project project, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException {
+ Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> remapper = _getTinyRemapper(project, fixRecords, builderConsumer);
+ remapper.getLeft().readClassPath(getMinecraftDependencies(project));
+ remapper.getLeft().prepareClasses();
+ return remapper;
}
public static Path[] getMinecraftDependencies(Project project) {
diff --git a/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java b/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java
new file mode 100644
index 00000000..06fd7e86
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java
@@ -0,0 +1,118 @@
+/*
+ * 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.util.aw2at;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.cadixdev.at.AccessChange;
+import org.cadixdev.at.AccessTransform;
+import org.cadixdev.at.AccessTransformSet;
+import org.cadixdev.at.ModifierChange;
+import org.cadixdev.bombe.type.signature.MethodSignature;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
+
+import net.fabricmc.accesswidener.AccessWidenerReader;
+import net.fabricmc.accesswidener.AccessWidenerVisitor;
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.task.RemapJarTask;
+
+/**
+ * Converts AW files to AT files.
+ *
+ * @author Juuz
+ */
+public final class Aw2At {
+ public static void setup(Project project, RemapJarTask remapJar) {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+
+ if (extension.getAccessWidenerPath().isPresent()) {
+ // Find the relative AW file name
+ String awName = null;
+ Path awPath = extension.getAccessWidenerPath().get().getAsFile().toPath();
+ SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
+ SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+ boolean found = false;
+
+ for (File srcDir : main.getResources().getSrcDirs()) {
+ Path srcDirPath = srcDir.toPath().toAbsolutePath();
+
+ if (awPath.startsWith(srcDirPath)) {
+ awName = srcDirPath.relativize(awPath).toString().replace(File.separator, "/");
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ awName = awPath.getFileName().toString();
+ }
+
+ remapJar.getAtAccessWideners().add(awName);
+ }
+
+ remapJar.getAtAccessWideners().addAll(extension.getForge().getExtraAccessWideners());
+ }
+
+ public static AccessTransformSet toAccessTransformSet(BufferedReader reader) throws IOException {
+ AccessTransformSet atSet = AccessTransformSet.create();
+
+ new AccessWidenerReader(new AccessWidenerVisitor() {
+ @Override
+ public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) {
+ atSet.getOrCreateClass(name).merge(toAt(access));
+ }
+
+ @Override
+ public void visitMethod(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) {
+ atSet.getOrCreateClass(owner).mergeMethod(MethodSignature.of(name, descriptor), toAt(access));
+ }
+
+ @Override
+ public void visitField(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) {
+ atSet.getOrCreateClass(owner).mergeField(name, toAt(access));
+ }
+ }).read(reader);
+
+ return atSet;
+ }
+
+ private static AccessTransform toAt(AccessWidenerReader.AccessType access) {
+ return switch (access) {
+ // FIXME: This behaviour doesn't match what the actual AW does for methods.
+ // - accessible makes the method final if it was private
+ // - extendable makes the method protected if it was (package-)private
+ // Neither of these can be achieved with Forge ATs without using bytecode analysis.
+ // However, this might be good enough for us. (The effects only apply in prod.)
+ case ACCESSIBLE -> AccessTransform.of(AccessChange.PUBLIC);
+ case EXTENDABLE -> AccessTransform.of(AccessChange.PUBLIC, ModifierChange.REMOVE);
+ case MUTABLE -> AccessTransform.of(ModifierChange.REMOVE);
+ };
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java b/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java
new file mode 100644
index 00000000..389dbbef
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java
@@ -0,0 +1,75 @@
+/*
+ * 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.util.function;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Stream-like utilities for working with collections.
+ *
+ * @author Juuz
+ */
+public final class CollectionUtil {
+ /**
+ * Finds the first element matching the predicate.
+ *
+ * @param collection the collection to be searched
+ * @param filter the predicate to be matched
+ * @param <E> the element type
+ * @return the first matching element, or empty if none match
+ */
+ public static <E> Optional<E> find(Iterable<? extends E> collection, Predicate<? super E> filter) {
+ for (E e : collection) {
+ if (filter.test(e)) {
+ return Optional.of(e);
+ }
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Transforms the collection with a function.
+ *
+ * @param collection the source collection
+ * @param transform the transformation function
+ * @param <A> the source type
+ * @param <B> the target type
+ * @return a mutable list with the transformed entries
+ */
+ public static <A, B> List<B> map(Iterable<? extends A> collection, Function<? super A, ? extends B> transform) {
+ ArrayList<B> result = new ArrayList<>();
+
+ for (A a : collection) {
+ result.add(transform.apply(a));
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java b/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java
new file mode 100644
index 00000000..536721c7
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java
@@ -0,0 +1,39 @@
+/*
+ * 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.util.function;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+
+/**
+ * Consumes two file systems and corresponding path objects.
+ *
+ * @author Juuz
+ */
+@FunctionalInterface
+public interface FsPathConsumer {
+ void accept(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException;
+}
diff --git a/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java b/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java
new file mode 100644
index 00000000..379d4293
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.util.function;
+
+import java.io.IOException;
+
+/**
+ * Like Consumer, but can throw IOException.
+ *
+ * @param <A> the result type
+ * @author Juuz
+ */
+@FunctionalInterface
+public interface IoConsumer<A> {
+ void accept(A a) throws IOException;
+}
diff --git a/src/main/java/net/fabricmc/loom/util/function/LazyBool.java b/src/main/java/net/fabricmc/loom/util/function/LazyBool.java
new file mode 100644
index 00000000..7d2933a4
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/function/LazyBool.java
@@ -0,0 +1,52 @@
+/*
+ * 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.util.function;
+
+import java.util.Objects;
+import java.util.function.BooleanSupplier;
+
+/**
+ * A lazily computed boolean value.
+ *
+ * @author Juuz
+ */
+public final class LazyBool implements BooleanSupplier {
+ private BooleanSupplier supplier;
+ private Boolean value;
+
+ public LazyBool(BooleanSupplier supplier) {
+ this.supplier = Objects.requireNonNull(supplier, "supplier");
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ if (value == null) {
+ value = supplier.getAsBoolean();
+ supplier = null; // Release the supplier
+ }
+
+ return value;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java
new file mode 100644
index 00000000..8e1d3008
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java
@@ -0,0 +1,140 @@
+/*
+ * 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.util.srg;
+
+import java.io.IOException;
+import java.io.StringReader;
+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.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.UnaryOperator;
+
+import com.google.common.collect.ImmutableMap;
+import org.gradle.api.logging.Logger;
+
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.function.CollectionUtil;
+import net.fabricmc.mappingio.tree.MappingTree;
+
+/**
+ * Remaps AT classes from SRG to Yarn.
+ *
+ * @author Juuz
+ */
+public final class AtRemapper {
+ public static void remap(Logger logger, Path jar, MappingTree mappings) throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toUri()), ImmutableMap.of("create", false))) {
+ Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH);
+
+ if (Files.exists(atPath)) {
+ String atContent = Files.readString(atPath);
+
+ String[] lines = atContent.split("\n");
+ List<String> output = new ArrayList<>(lines.length);
+
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i].trim();
+
+ if (line.startsWith("#") || line.isBlank()) {
+ output.add(i, line);
+ continue;
+ }
+
+ String[] parts = line.split("\\s+");
+
+ if (parts.length < 2) {
+ logger.warn("Invalid AT Line: " + line);
+ output.add(i, line);
+ continue;
+ }
+
+ String name = parts[1].replace('.', '/');
+ parts[1] = CollectionUtil.find(
+ mappings.getClasses(),
+ def -> def.getName("srg").equals(name)
+ ).map(def -> def.getName("named")).orElse(name).replace('/', '.');
+
+ if (parts.length >= 3) {
+ if (parts[2].contains("(")) {
+ parts[2] = parts[2].substring(0, parts[2].indexOf('(')) + remapDescriptor(parts[2].substring(parts[2].indexOf('(')), s -> {
+ return CollectionUtil.find(
+ mappings.getClasses(),
+ def -> def.getName("srg").equals(s)
+ ).map(def -> def.getName("named")).orElse(s);
+ });
+ }
+ }
+
+ output.add(i, String.join(" ", parts));
+ }
+
+ Files.write(atPath, String.join("\n", output).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
+ }
+ }
+ }
+
+ private static String remapDescriptor(String original, UnaryOperator<String> classMappings) {
+ try {
+ StringReader reader = new StringReader(original);
+ StringBuilder result = new StringBuilder();
+ boolean insideClassName = false;
+ StringBuilder className = new StringBuilder();
+
+ while (true) {
+ int c = reader.read();
+
+ if (c == -1) {
+ break;
+ }
+
+ if ((char) c == ';') {
+ insideClassName = false;
+ result.append(classMappings.apply(className.toString()));
+ }
+
+ if (insideClassName) {
+ className.append((char) c);
+ } else {
+ result.append((char) c);
+ }
+
+ if (!insideClassName && (char) c == 'L') {
+ insideClassName = true;
+ className.setLength(0);
+ }
+ }
+
+ return result.toString();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java
new file mode 100644
index 00000000..535bcb21
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util.srg;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+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.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.logging.Logger;
+
+import net.fabricmc.loom.util.function.CollectionUtil;
+import net.fabricmc.mappingio.tree.MappingTree;
+
+/**
+ * Remaps coremod class names from SRG to Yarn.
+ *
+ * @author Juuz
+ */
+public final class CoreModClassRemapper {
+ private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^(.*')((?:com\\.mojang\\.|net\\.minecraft\\.)[A-Za-z0-9.-_$]+)('.*)$");
+
+ public static void remapJar(Path jar, MappingTree mappings, Logger logger) throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toUri()), ImmutableMap.of("create", false))) {
+ Path coremodsJsonPath = fs.getPath("META-INF", "coremods.json");
+
+ if (Files.notExists(coremodsJsonPath)) {
+ logger.info(":no coremods in " + jar.getFileName());
+ return;
+ }
+
+ JsonObject coremodsJson;
+
+ try (Reader reader = Files.newBufferedReader(coremodsJsonPath)) {
+ coremodsJson = new Gson().fromJson(reader, JsonObject.class);
+ }
+
+ for (Map.Entry<String, JsonElement> nameFileEntry : coremodsJson.entrySet()) {
+ String file = nameFileEntry.getValue().getAsString();
+ Path js = fs.getPath(file);
+
+ if (Files.exists(js)) {
+ logger.info(":remapping coremod '" + file + "'");
+ remap(js, mappings);
+ } else {
+ logger.warn("Coremod '" + file + "' listed in coremods.json but not found");
+ }
+ }
+ }
+ }
+
+ public static void remap(Path js, MappingTree mappings) throws IOException {
+ List<String> lines = Files.readAllLines(js);
+ List<String> output = new ArrayList<>(lines);
+
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ Matcher matcher = CLASS_NAME_PATTERN.matcher(line);
+
+ if (matcher.matches()) {
+ String className = matcher.group(2).replace('.', '/');
+ String remapped = CollectionUtil.find(mappings.getClasses(), def -> def.getName("srg").equals(className))
+ .map(def -> def.getName("named"))
+ .orElse(className);
+
+ if (!className.equals(remapped)) {
+ output.set(i, matcher.group(1) + remapped.replace('/', '.') + matcher.group(3));
+ }
+ }
+ }
+
+ if (!lines.equals(output)) {
+ try (Writer writer = Files.newBufferedWriter(js, StandardCharsets.UTF_8, StandardOpenOption.WRITE)) {
+ writer.write(String.join("\n", output));
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java
new file mode 100644
index 00000000..e769c2f4
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util.srg;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import dev.architectury.tinyremapper.IMappingProvider;
+
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.mappingio.tree.MappingTree;
+
+public class InnerClassRemapper {
+ public static IMappingProvider of(Set<String> fromClassNames, MappingTree mappingsWithSrg, String from, String to) throws IOException {
+ return sink -> {
+ remapInnerClass(fromClassNames, mappingsWithSrg, from, to, sink::acceptClass);
+ };
+ }
+
+ public static Set<String> readClassNames(Path jar) {
+ Set<String> set = new HashSet<>();
+
+ try (FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(jar, false)) {
+ Iterator<Path> iterator = Files.walk(system.get().getPath("/")).iterator();
+
+ while (iterator.hasNext()) {
+ Path path = iterator.next();
+ String name = path.toString();
+ if (name.startsWith("/")) name = name.substring(1);
+
+ if (!Files.isDirectory(path) && name.contains("$") && name.endsWith(".class")) {
+ String className = name.substring(0, name.length() - 6);
+ set.add(className);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return set;
+ }
+
+ private static void remapInnerClass(Set<String> classNames, MappingTree mappingsWithSrg, String from, String to, BiConsumer<String, String> action) {
+ BiMap<String, String> availableClasses = HashBiMap.create(mappingsWithSrg.getClasses().stream()
+ .collect(Collectors.groupingBy(classDef -> classDef.getName(from),
+ Collectors.<MappingTree.ClassMapping, String>reducing(
+ null,
+ classDef -> classDef.getName(to),
+ (first, last) -> last
+ ))
+ ));
+
+ for (String className : classNames) {
+ if (!availableClasses.containsKey(className)) {
+ String parentName = className.substring(0, className.indexOf('$'));
+ String childName = className.substring(className.indexOf('$') + 1);
+ String remappedParentName = availableClasses.getOrDefault(parentName, parentName);
+ String remappedName = remappedParentName + "$" + childName;
+
+ if (!className.equals(remappedName)) {
+ if (availableClasses.containsValue(remappedName)) {
+ // https://github.com/MinecraftForge/MinecraftForge/blob/b027a92dd287d6810a9fdae4d4b1e1432d7dc9cc/patches/minecraft/net/minecraft/Util.java.patch#L8
+ action.accept(className, remappedName + "_UNBREAK");
+ } else {
+ action.accept(className, remappedName);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java
new file mode 100644
index 00000000..96382bb5
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java
@@ -0,0 +1,363 @@
+/*
+ * 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.util.srg;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+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.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.opencsv.CSVReader;
+import com.opencsv.exceptions.CsvValidationException;
+import org.apache.commons.io.IOUtils;
+import org.cadixdev.lorenz.MappingSet;
+import org.cadixdev.lorenz.io.srg.tsrg.TSrgReader;
+import org.cadixdev.lorenz.model.ClassMapping;
+import org.cadixdev.lorenz.model.FieldMapping;
+import org.cadixdev.lorenz.model.InnerClassMapping;
+import org.cadixdev.lorenz.model.MethodMapping;
+import org.cadixdev.lorenz.model.TopLevelClassMapping;
+import org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.mappingio.MappingReader;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+import net.fabricmc.stitch.commands.tinyv2.TinyClass;
+import net.fabricmc.stitch.commands.tinyv2.TinyField;
+import net.fabricmc.stitch.commands.tinyv2.TinyFile;
+import net.fabricmc.stitch.commands.tinyv2.TinyMethod;
+import net.fabricmc.stitch.commands.tinyv2.TinyMethodParameter;
+import net.fabricmc.stitch.commands.tinyv2.TinyV2Reader;
+
+public class MCPReader {
+ private final Path intermediaryTinyPath;
+ private final Path srgTsrgPath;
+
+ public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath) {
+ this.intermediaryTinyPath = intermediaryTinyPath;
+ this.srgTsrgPath = srgTsrgPath;
+ }
+
+ public TinyFile read(Path mcpJar) throws IOException {
+ Map<MemberToken, String> srgTokens = readSrg();
+ TinyFile intermediaryTiny = TinyV2Reader.read(intermediaryTinyPath);
+ Map<String, String> intermediaryToMCPMap = createIntermediaryToMCPMap(intermediaryTiny, srgTokens);
+ Map<String, String[]> intermediaryToDocsMap = new HashMap<>();
+ Map<String, Map<Integer, String>> intermediaryToParamsMap = new HashMap<>();
+
+ try {
+ injectMcp(mcpJar, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap);
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
+ }
+
+ mergeTokensIntoIntermediary(intermediaryTiny, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap);
+ return intermediaryTiny;
+ }
+
+ private Map<String, String> createIntermediaryToMCPMap(TinyFile tiny, Map<MemberToken, String> officialToMCP) {
+ Map<String, String> map = new HashMap<>();
+
+ for (TinyClass tinyClass : tiny.getClassEntries()) {
+ String classObf = tinyClass.getMapping().get(0);
+ String classIntermediary = tinyClass.getMapping().get(1);
+ MemberToken classTokenObf = MemberToken.ofClass(classObf);
+
+ if (officialToMCP.containsKey(classTokenObf)) {
+ map.put(classIntermediary, officialToMCP.get(classTokenObf));
+ }
+
+ for (TinyField tinyField : tinyClass.getFields()) {
+ String fieldObf = tinyField.getMapping().get(0);
+ String fieldIntermediary = tinyField.getMapping().get(1);
+ MemberToken fieldTokenObf = MemberToken.ofField(classTokenObf, fieldObf);
+
+ if (officialToMCP.containsKey(fieldTokenObf)) {
+ map.put(fieldIntermediary, officialToMCP.get(fieldTokenObf));
+ }
+ }
+
+ for (TinyMethod tinyMethod : tinyClass.getMethods()) {
+ String methodObf = tinyMethod.getMapping().get(0);
+ String methodIntermediary = tinyMethod.getMapping().get(1);
+ MemberToken methodTokenObf = MemberToken.ofMethod(classTokenObf, methodObf, tinyMethod.getMethodDescriptorInFirstNamespace());
+
+ if (officialToMCP.containsKey(methodTokenObf)) {
+ map.put(methodIntermediary, officialToMCP.get(methodTokenObf));
+ }
+ }
+ }
+
+ return map;
+ }
+
+ private void mergeTokensIntoIntermediary(TinyFile tiny, Map<String, String> intermediaryToMCPMap, Map<String, String[]> intermediaryToDocsMap, Map<String, Map<Integer, String>> intermediaryToParamsMap) {
+ stripTinyWithParametersAndLocal(tiny);
+
+ // We will be adding the "named" namespace with MCP
+ tiny.getHeader().getNamespaces().add("named");
+
+ for (TinyClass tinyClass : tiny.getClassEntries()) {
+ String classIntermediary = tinyClass.getMapping().get(1);
+ tinyClass.getMapping().add(intermediaryToMCPMap.getOrDefault(classIntermediary, classIntermediary));
+
+ for (TinyField tinyField : tinyClass.getFields()) {
+ String fieldIntermediary = tinyField.getMapping().get(1);
+ String[] docs = intermediaryToDocsMap.get(fieldIntermediary);
+ tinyField.getMapping().add(intermediaryToMCPMap.getOrDefault(fieldIntermediary, fieldIntermediary));
+
+ if (docs != null) {
+ tinyField.getComments().clear();
+ tinyField.getComments().addAll(Arrays.asList(docs));
+ }
+ }
+
+ for (TinyMethod tinyMethod : tinyClass.getMethods()) {
+ String methodIntermediary = tinyMethod.getMapping().get(1);
+ String[] docs = intermediaryToDocsMap.get(methodIntermediary);
+ tinyMethod.getMapping().add(intermediaryToMCPMap.getOrDefault(methodIntermediary, methodIntermediary));
+
+ if (docs != null) {
+ tinyMethod.getComments().clear();
+ tinyMethod.getComments().addAll(Arrays.asList(docs));
+ }
+
+ Map<Integer, String> params = intermediaryToParamsMap.get(methodIntermediary);
+
+ if (params != null) {
+ for (Map.Entry<Integer, String> entry : params.entrySet()) {
+ int lvIndex = entry.getKey();
+ String paramName = entry.getValue();
+
+ ArrayList<String> mappings = new ArrayList<>();
+ mappings.add("");
+ mappings.add("");
+ mappings.add(paramName);
+ tinyMethod.getParameters().add(new TinyMethodParameter(lvIndex, mappings, new ArrayList<>()));
+ }
+ }
+ }
+ }
+ }
+
+ private void stripTinyWithParametersAndLocal(TinyFile tiny) {
+ for (TinyClass tinyClass : tiny.getClassEntries()) {
+ for (TinyMethod tinyMethod : tinyClass.getMethods()) {
+ tinyMethod.getParameters().clear();
+ tinyMethod.getLocalVariables().clear();
+ }
+ }
+ }
+
+ private Map<MemberToken, String> readSrg() throws IOException {
+ Map<MemberToken, String> tokens = new HashMap<>();
+
+ try (BufferedReader reader = Files.newBufferedReader(srgTsrgPath, StandardCharsets.UTF_8)) {
+ String content = IOUtils.toString(reader);
+
+ if (content.startsWith("tsrg2")) {
+ readTsrg2(tokens, content);
+ } else {
+ MappingSet mappingSet = new TSrgReader(new StringReader(content)).read();
+
+ for (TopLevelClassMapping classMapping : mappingSet.getTopLevelClassMappings()) {
+ appendClass(tokens, classMapping);
+ }
+ }
+ }
+
+ return tokens;
+ }
+
+ private void readTsrg2(Map<MemberToken, String> tokens, String content) throws IOException {
+ MemoryMappingTree tree = new MemoryMappingTree();
+ MappingReader.read(new StringReader(content), tree);
+ int obfIndex = tree.getNamespaceId("obf");
+ int srgIndex = tree.getNamespaceId("srg");
+
+ for (MappingTree.ClassMapping classDef : tree.getClasses()) {
+ MemberToken ofClass = MemberToken.ofClass(classDef.getName(obfIndex));
+ tokens.put(ofClass, classDef.getName(srgIndex));
+
+ for (MappingTree.FieldMapping fieldDef : classDef.getFields()) {
+ tokens.put(MemberToken.ofField(ofClass, fieldDef.getName(obfIndex)), fieldDef.getName(srgIndex));
+ }
+
+ for (MappingTree.MethodMapping methodDef : classDef.getMethods()) {
+ tokens.put(MemberToken.ofMethod(ofClass, methodDef.getName(obfIndex), methodDef.getDesc(obfIndex)),
+ methodDef.getName(srgIndex));
+ }
+ }
+ }
+
+ private void injectMcp(Path mcpJar, Map<String, String> intermediaryToSrgMap, Map<String, String[]> intermediaryToDocsMap, Map<String, Map<Integer, String>> intermediaryToParamsMap)
+ throws IOException, CsvValidationException {
+ Map<String, List<String>> srgToIntermediary = inverseMap(intermediaryToSrgMap);
+ Map<String, List<String>> simpleSrgToIntermediary = new HashMap<>();
+ Pattern methodPattern = Pattern.compile("(func_\\d*)_.*");
+
+ for (Map.Entry<String, List<String>> entry : srgToIntermediary.entrySet()) {
+ Matcher matcher = methodPattern.matcher(entry.getKey());
+
+ if (matcher.matches()) {
+ simpleSrgToIntermediary.put(matcher.group(1), entry.getValue());
+ }
+ }
+
+ try (FileSystem fs = FileSystems.newFileSystem(mcpJar, (ClassLoader) null)) {
+ Path fields = fs.getPath("fields.csv");
+ Path methods = fs.getPath("methods.csv");
+ Path params = fs.getPath("params.csv");
+ Pattern paramsPattern = Pattern.compile("p_[^\\d]*(\\d+)_(\\d)+_?");
+
+ try (CSVReader reader = new CSVReader(Files.newBufferedReader(fields, StandardCharsets.UTF_8))) {
+ reader.readNext();
+ String[] line;
+
+ while ((line = reader.readNext()) != null) {
+ List<String> intermediaryField = srgToIntermediary.get(line[0]);
+ String[] docs = line[3].split("\n");
+
+ if (intermediaryField != null) {
+ for (String s : intermediaryField) {
+ intermediaryToSrgMap.put(s, line[1]);
+
+ if (!line[3].trim().isEmpty() && docs.length > 0) {
+ intermediaryToDocsMap.put(s, docs);
+ }
+ }
+ }
+ }
+ }
+
+ try (CSVReader reader = new CSVReader(Files.newBufferedReader(methods, StandardCharsets.UTF_8))) {
+ reader.readNext();
+ String[] line;
+
+ while ((line = reader.readNext()) != null) {
+ List<String> intermediaryMethod = srgToIntermediary.get(line[0]);
+ String[] docs = line[3].split("\n");
+
+ if (intermediaryMethod != null) {
+ for (String s : intermediaryMethod) {
+ intermediaryToSrgMap.put(s, line[1]);
+
+ if (!line[3].trim().isEmpty() && docs.length > 0) {
+ intermediaryToDocsMap.put(s, docs);
+ }
+ }
+ }
+ }
+ }
+
+ if (Files.exists(params)) {
+ try (CSVReader reader = new CSVReader(Files.newBufferedReader(params, StandardCharsets.UTF_8))) {
+ reader.readNext();
+ String[] line;
+
+ while ((line = reader.readNext()) != null) {
+ Matcher param = paramsPattern.matcher(line[0]);
+
+ if (param.matches()) {
+ String named = line[1];
+ String srgMethodStartWith = "func_" + param.group(1);
+ int lvIndex = Integer.parseInt(param.group(2));
+ List<String> intermediaryMethod = simpleSrgToIntermediary.get(srgMethodStartWith);
+
+ if (intermediaryMethod != null) {
+ for (String s : intermediaryMethod) {
+ intermediaryToParamsMap.computeIfAbsent(s, s1 -> new HashMap<>()).put(lvIndex, named);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private Map<String, List<String>> inverseMap(Map<String, String> intermediaryToMCPMap) {
+ Map<String, List<String>> map = new HashMap<>();
+
+ for (Map.Entry<String, String> token : intermediaryToMCPMap.entrySet()) {
+ map.computeIfAbsent(token.getValue(), s -> new ArrayList<>()).add(token.getKey());
+ }
+
+ return map;
+ }
+
+ private void appendClass(Map<MemberToken, String> tokens, ClassMapping<?, ?> classMapping) {
+ MemberToken ofClass = MemberToken.ofClass(classMapping.getFullObfuscatedName());
+ tokens.put(ofClass, classMapping.getFullDeobfuscatedName());
+
+ for (FieldMapping fieldMapping : classMapping.getFieldMappings()) {
+ tokens.put(MemberToken.ofField(ofClass, fieldMapping.getObfuscatedName()), fieldMapping.getDeobfuscatedName());
+ }
+
+ for (MethodMapping methodMapping : classMapping.getMethodMappings()) {
+ tokens.put(MemberToken.ofMethod(ofClass, methodMapping.getObfuscatedName(), methodMapping.getObfuscatedDescriptor()), methodMapping.getDeobfuscatedName());
+ }
+
+ for (InnerClassMapping mapping : classMapping.getInnerClassMappings()) {
+ appendClass(tokens, mapping);
+ }
+ }
+
+ private record MemberToken(
+ TokenType type,
+ @Nullable MCPReader.MemberToken owner,
+ String name,
+ @Nullable String descriptor
+ ) {
+ static MemberToken ofClass(String name) {
+ return new MemberToken(TokenType.CLASS, null, name, null);
+ }
+
+ static MemberToken ofField(MemberToken owner, String name) {
+ return new MemberToken(TokenType.FIELD, owner, name, null);
+ }
+
+ static MemberToken ofMethod(MemberToken owner, String name, String descriptor) {
+ return new MemberToken(TokenType.METHOD, owner, name, descriptor);
+ }
+ }
+
+ private enum TokenType {
+ CLASS,
+ METHOD,
+ FIELD
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/MappingException.java b/src/main/java/net/fabricmc/loom/util/srg/MappingException.java
new file mode 100644
index 00000000..5bf2a63b
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/MappingException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.util.srg;
+
+/**
+ * An exception that occurs when processing obfuscation mappings.
+ *
+ * @author Juuz
+ */
+public class MappingException extends RuntimeException {
+ public MappingException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java
new file mode 100644
index 00000000..59392e94
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java
@@ -0,0 +1,148 @@
+/*
+ * 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.util.srg;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.google.common.base.Stopwatch;
+import org.apache.commons.io.output.NullOutputStream;
+import org.gradle.api.Project;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.configuration.ShowStacktrace;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider.RemapAction;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.ThreadingUtils;
+
+public class SpecialSourceExecutor {
+ private static String trimLeadingSlash(String string) {
+ if (string.startsWith(File.separator)) {
+ return string.substring(File.separator.length());
+ } else if (string.startsWith("/")) {
+ return string.substring(1);
+ }
+
+ return string;
+ }
+
+ public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set<File> mcLibs, Path officialJar, Path mappings)
+ throws Exception {
+ Set<String> filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream()
+ .filter(s -> !s.startsWith("\t"))
+ .map(s -> s.split(" ")[0] + ".class")
+ .collect(Collectors.toSet());
+ LoomGradleExtension extension = LoomGradleExtension.get(project.getProject());
+ Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar");
+ Files.deleteIfExists(stripped);
+
+ Stopwatch stopwatch = Stopwatch.createStarted();
+
+ try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(stripped, true)) {
+ try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(officialJar, false)) {
+ ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter();
+
+ for (Path path : (Iterable<? extends Path>) Files.walk(fs.get().getPath("/"))::iterator) {
+ String trimLeadingSlash = trimLeadingSlash(path.toString());
+ if (!trimLeadingSlash.endsWith(".class")) continue;
+ boolean has = filter.contains(trimLeadingSlash);
+ String s = trimLeadingSlash;
+
+ while (s.contains("$") && !has) {
+ s = s.substring(0, s.lastIndexOf("$")) + ".class";
+ has = filter.contains(s);
+ }
+
+ if (!has) continue;
+ Path to = output.get().getPath(trimLeadingSlash);
+ Path parent = to.getParent();
+ if (parent != null) Files.createDirectories(parent);
+
+ completer.add(() -> {
+ Files.copy(path, to, StandardCopyOption.COPY_ATTRIBUTES);
+ });
+ }
+
+ completer.complete();
+ }
+ } finally {
+ project.getLogger().info("Copied class files in " + stopwatch.stop());
+ }
+
+ Path output = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar");
+ Files.deleteIfExists(output);
+ stopwatch = Stopwatch.createStarted();
+
+ List<String> args = remapAction.getArgs(stripped, output, mappings, project.files(mcLibs));
+
+ project.getLogger().lifecycle(":remapping minecraft (" + remapAction + ", " + side + ", official -> mojang)");
+
+ Path workingDir = tmpDir();
+
+ project.javaexec(spec -> {
+ spec.setArgs(args);
+ spec.setClasspath(remapAction.getClasspath());
+ spec.workingDir(workingDir.toFile());
+ spec.getMainClass().set(remapAction.getMainClass());
+
+ // if running with INFO or DEBUG logging
+ if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS
+ || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) {
+ spec.setStandardOutput(System.out);
+ spec.setErrorOutput(System.err);
+ } else {
+ spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM);
+ spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM);
+ }
+ }).rethrowFailure().assertNormalExitValue();
+
+ project.getLogger().lifecycle(":remapped minecraft (" + remapAction + ", " + side + ", official -> mojang) in " + stopwatch.stop());
+
+ Files.deleteIfExists(stripped);
+
+ Path tmp = tmpFile();
+ Files.deleteIfExists(tmp);
+ Files.copy(output, tmp);
+
+ Files.deleteIfExists(output);
+ return tmp;
+ }
+
+ private static Path tmpFile() throws IOException {
+ return Files.createTempFile(null, null);
+ }
+
+ private static Path tmpDir() throws IOException {
+ return Files.createTempDirectory(null);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java
new file mode 100644
index 00000000..ede2dbad
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java
@@ -0,0 +1,406 @@
+/*
+ * 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.util.srg;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Stopwatch;
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.logging.Logger;
+import org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.loom.util.function.CollectionUtil;
+import net.fabricmc.mappingio.FlatMappingVisitor;
+import net.fabricmc.mappingio.MappingReader;
+import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor;
+import net.fabricmc.mappingio.format.Tiny2Writer;
+import net.fabricmc.mappingio.format.TsrgReader;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MappingTreeView;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+/**
+ * Utilities for merging SRG mappings.
+ *
+ * @author Juuz
+ */
+public final class SrgMerger {
+ private final Logger logger;
+ private final Map<String, List<MappingTreeView.MemberMappingView>> addRegardlessSrg = new HashMap<>();
+ private final MemoryMappingTree srg;
+ private final MemoryMappingTree src;
+ private final MemoryMappingTree output;
+ private final FlatMappingVisitor flatOutput;
+ private final List<Runnable> postProcesses = new ArrayList<>();
+ private final boolean lenient;
+ private final Set<String> methodSrgNames = new HashSet<>();
+
+ public SrgMerger(Logger logger, Path srg, @Nullable Supplier<Path> mojmap, Path tiny, boolean lenient) throws IOException {
+ this.logger = logger;
+ this.srg = readSrg(srg, mojmap);
+ this.src = new MemoryMappingTree();
+ this.output = new MemoryMappingTree();
+ this.flatOutput = new RegularAsFlatMappingVisitor(output);
+ this.lenient = lenient;
+
+ MappingReader.read(tiny, this.src);
+
+ if (!"official".equals(this.src.getSrcNamespace())) {
+ throw new MappingException("Mapping file " + tiny + " does not have the 'official' namespace as the default!");
+ }
+
+ this.output.visitNamespaces(this.src.getSrcNamespace(), Stream.concat(Stream.of("srg"), this.src.getDstNamespaces().stream()).collect(Collectors.toList()));
+ }
+
+ public MemoryMappingTree merge() throws IOException {
+ for (MappingTree.ClassMapping klass : this.srg.getClasses()) {
+ classToTiny(klass);
+ }
+
+ try {
+ for (Runnable process : postProcesses) {
+ process.run();
+ }
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
+ }
+
+ return output;
+ }
+
+ /**
+ * Merges SRG mappings with a tiny mappings tree through the obf names.
+ *
+ * @param srg the SRG file in .tsrg format
+ * @param mojmap the path to the mojmap file used for generating mojmap+srg names, may be null
+ * @param tiny the tiny file
+ * @param out the output file, will be in tiny v2
+ * @param lenient whether to ignore missing tiny mapping
+ * @throws IOException if an IO error occurs while reading or writing the mappings
+ * @throws MappingException if the input tiny tree's default namespace is not 'official'
+ * or if an element mentioned in the SRG file does not have tiny mappings
+ */
+ public static void mergeSrg(Logger logger, @Nullable Supplier<Path> mojmap, Path srg, Path tiny, Path out, boolean lenient)
+ throws IOException, MappingException {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient);
+
+ try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out), false)) {
+ tree.accept(writer);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ logger.info(":merged srg mappings in " + stopwatch.stop());
+ }
+
+ /**
+ * Merges SRG mappings with a tiny mappings tree through the obf names.
+ *
+ * @param srg the SRG file in .tsrg format
+ * @param mojmap the path to the mojmap file used for generating mojmap+srg names, may be null
+ * @param tiny the tiny file
+ * @param lenient whether to ignore missing tiny mapping
+ * @return the created mapping tree
+ * @throws IOException if an IO error occurs while reading or writing the mappings
+ * @throws MappingException if the input tiny tree's default namespace is not 'official'
+ * or if an element mentioned in the SRG file does not have tiny mappings
+ */
+ public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier<Path> mojmap, Path srg, Path tiny, boolean lenient)
+ throws IOException, MappingException {
+ return new SrgMerger(logger, srg, mojmap, tiny, lenient).merge();
+ }
+
+ private MemoryMappingTree readSrg(Path srg, @Nullable Supplier<Path> mojmap) throws IOException {
+ try (BufferedReader reader = Files.newBufferedReader(srg)) {
+ String content = IOUtils.toString(reader);
+
+ if (content.startsWith("tsrg2") && mojmap != null) {
+ addRegardlessSrgs(mojmap);
+ }
+
+ MemoryMappingTree tsrg = new MemoryMappingTree();
+ TsrgReader.read(new StringReader(content), tsrg);
+ return tsrg;
+ }
+ }
+
+ private void addRegardlessSrgs(Supplier<Path> mojmap) throws IOException {
+ MemoryMappingTree mojmapTree = readTsrg2ToTinyTree(mojmap.get());
+
+ for (MappingTree.ClassMapping classDef : mojmapTree.getClasses()) {
+ for (MappingTree.MethodMapping methodDef : classDef.getMethods()) {
+ String name = methodDef.getSrcName();
+
+ if (name.indexOf('<') != 0 && name.equals(methodDef.getDstName(0))) {
+ addRegardlessSrg.computeIfAbsent(classDef.getSrcName(), $ -> new ArrayList<>()).add(methodDef);
+ }
+ }
+
+ for (MappingTree.FieldMapping fieldDef : classDef.getFields()) {
+ if (fieldDef.getSrcName().equals(fieldDef.getDstName(0))) {
+ addRegardlessSrg.computeIfAbsent(classDef.getSrcName(), $ -> new ArrayList<>()).add(fieldDef);
+ }
+ }
+ }
+ }
+
+ private static MemoryMappingTree readTsrg2ToTinyTree(Path path) throws IOException {
+ MemoryMappingTree tree = new MemoryMappingTree();
+
+ try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+ MappingReader.read(reader, tree);
+ }
+
+ return tree;
+ }
+
+ private void classToTiny(MappingTree.ClassMapping klass) throws IOException {
+ String obf = klass.getSrcName();
+ String srg = klass.getDstName(0);
+ MappingTree.ClassMapping classDef = this.src.getClass(obf);
+
+ if (classDef == null) {
+ if (lenient) {
+ return;
+ } else {
+ throw new MappingException("Missing class: " + obf + " (srg: " + srg + ")");
+ }
+ }
+
+ List<String> classNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace)
+ );
+
+ flatOutput.visitClass(obf, classNames.toArray(new String[0]));
+
+ if (classDef.getComment() != null) {
+ flatOutput.visitClassComment(obf, classDef.getComment());
+ }
+
+ for (MappingTree.MethodMapping method : klass.getMethods()) {
+ MappingTree.MethodMapping def = CollectionUtil.find(
+ classDef.getMethods(),
+ m -> m.getName("official").equals(method.getSrcName()) && m.getDesc("official").equals(method.getSrcDesc())
+ ).orElse(null);
+
+ String methodSrgName = method.getDstName(0);
+
+ if (def == null) {
+ if (lenient) {
+ // TODO Big Hack!
+ // We are checking if there are methods with the same srg name that are already added, if it is, then we would not fill in these names
+ // This is especially troublesome with methods annotated with @DontObfuscate (e.g. m_129629_)
+ // with environments like yarn where methods with the same srg name may not inherit the same names due to parameter mappings and inheritance
+ // This requires further testing!
+ postProcesses.add(() -> {
+ if (!methodSrgNames.contains(methodSrgName)) {
+ List<String> methodNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? methodSrgName : method.getSrcName()
+ );
+
+ try {
+ flatOutput.visitMethod(obf, method.getSrcName(), method.getSrcDesc(), methodNames.toArray(new String[0]));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ });
+ } else {
+ throw new MappingException("Missing method: " + method.getSrcName() + " (srg: " + methodSrgName + ")");
+ }
+
+ continue;
+ }
+
+ methodToTiny(obf, method, methodSrgName, def);
+
+ if (methodSrgName.startsWith("func_") || methodSrgName.startsWith("m_")) {
+ methodSrgNames.add(methodSrgName);
+ }
+ }
+
+ postProcesses.add(() -> {
+ // TODO: This second iteration seems a bit wasteful.
+ // Is it possible to just iterate this and leave SRG out?
+ for (MappingTree.MethodMapping def : classDef.getMethods()) {
+ // If obf = some other name: some special name that srg might not contain.
+ // This includes constructors and overridden JDK methods.
+ if (!def.getSrcName().equals(def.getDstName(0))) {
+ continue;
+ }
+
+ MappingTree.MethodMapping method = CollectionUtil.find(
+ klass.getMethods(),
+ m -> m.getSrcName().equals(def.getName("official")) && m.getSrcDesc().equals(def.getDesc("official"))
+ ).orElse(null);
+
+ if (method == null) {
+ try {
+ methodToTiny(obf, null, def.getSrcName(), def);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ }
+ });
+
+ for (MappingTree.FieldMapping field : klass.getFields()) {
+ MappingTree.FieldMapping def = CollectionUtil.find(
+ classDef.getFields(),
+ f -> f.getName("official").equals(field.getSrcName())
+ ).orElse(nullOrThrow(() -> new MappingException("Missing field: " + field.getSrcName() + " (srg: " + field.getDstName(0) + ")")));
+
+ if (def == null) {
+ if (tryMatchRegardlessSrgsField(obf, field)) {
+ List<String> fieldNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? field.getDstName(0) : field.getSrcName()
+ );
+
+ flatOutput.visitField(obf, field.getSrcName(), field.getSrcDesc(), fieldNames.toArray(new String[0]));
+ }
+
+ continue;
+ }
+
+ List<String> fieldNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? field.getDstName(0) : def.getName(namespace)
+ );
+
+ flatOutput.visitField(obf, def.getName("official"), def.getDesc("official"), fieldNames.toArray(new String[0]));
+
+ if (def.getComment() != null) {
+ flatOutput.visitFieldComment(obf, def.getName("official"), def.getDesc("official"), def.getComment());
+ }
+ }
+ }
+
+ private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMapping srgMethod, @Nullable String srgMethodName, MappingTree.MethodMapping actualMethod)
+ throws IOException {
+ if (srgMethod != null && srgMethodName != null) {
+ srgMethodName = srgMethod.getDstName(0);
+ }
+
+ String finalSrgMethodName = srgMethodName;
+ List<String> methodNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? finalSrgMethodName : actualMethod.getName(namespace)
+ );
+
+ flatOutput.visitMethod(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), methodNames.toArray(new String[0]));
+
+ if (actualMethod.getComment() != null) {
+ flatOutput.visitMethodComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), actualMethod.getComment());
+ }
+
+ for (MappingTree.MethodArgMapping arg : actualMethod.getArgs()) {
+ MappingTree.MethodArgMapping srgArg = srgMethod != null ? srgMethod.getArg(arg.getArgPosition(), arg.getLvIndex(), arg.getName("official")) : null;
+ String srgName = srgArg != null ? srgArg.getDstName(0) : null;
+ List<String> argNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? srgName : arg.getName(namespace)
+ );
+
+ flatOutput.visitMethodArg(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), arg.getArgPosition(), arg.getLvIndex(), arg.getName("official"), argNames.toArray(new String[0]));
+
+ if (arg.getComment() != null) {
+ flatOutput.visitMethodArgComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), arg.getArgPosition(), arg.getLvIndex(), arg.getName("official"), arg.getComment());
+ }
+ }
+
+ for (MappingTree.MethodVarMapping var : actualMethod.getVars()) {
+ MappingTree.MethodVarMapping srgVar = srgMethod != null ? srgMethod.getVar(var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official")) : null;
+ String srgName = srgVar != null ? srgVar.getDstName(0) : null;
+ List<String> varNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? srgName : var.getName(namespace)
+ );
+
+ flatOutput.visitMethodVar(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official"), varNames.toArray(new String[0]));
+
+ if (var.getComment() != null) {
+ flatOutput.visitMethodVarComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official"), var.getComment());
+ }
+ }
+ }
+
+ private boolean tryMatchRegardlessSrgsMethod(String obf, MappingTree.MethodMapping method) {
+ List<MappingTreeView.MemberMappingView> mutableDescriptoredList = addRegardlessSrg.get(obf);
+
+ if (!method.getDstName(0).equals(method.getSrcName())) {
+ for (MappingTreeView.MemberMappingView descriptored : MoreObjects.firstNonNull(mutableDescriptoredList, Collections.<MappingTreeView.MemberMappingView>emptyList())) {
+ if (descriptored instanceof MappingTree.MethodMapping && descriptored.getSrcName().equals(method.getSrcName()) && descriptored.getSrcDesc().equals(method.getSrcDesc())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean tryMatchRegardlessSrgsField(String obf, MappingTree.FieldMapping field) {
+ List<MappingTreeView.MemberMappingView> mutableDescriptoredList = addRegardlessSrg.get(obf);
+
+ if (!field.getDstName(0).equals(field.getSrcName())) {
+ for (MappingTreeView.MemberMappingView descriptored : MoreObjects.firstNonNull(mutableDescriptoredList, Collections.<MappingTreeView.MemberMappingView>emptyList())) {
+ if (descriptored instanceof MappingTree.FieldMapping && descriptored.getSrcName().equals(field.getSrcName())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Nullable
+ private <T, X extends Exception> T nullOrThrow(Supplier<X> exception) throws X {
+ if (lenient) {
+ return null;
+ } else {
+ throw exception.get();
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
new file mode 100644
index 00000000..456b8b1b
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util.srg;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.cadixdev.lorenz.io.srg.SrgWriter;
+import org.gradle.api.logging.Logger;
+
+import net.fabricmc.lorenztiny.TinyMappingsReader;
+import net.fabricmc.mappingio.tree.MappingTree;
+
+public class SrgNamedWriter {
+ public static void writeTo(Logger logger, Path srgFile, MappingTree mappings, String from, String to) throws IOException {
+ Files.deleteIfExists(srgFile);
+
+ try (SrgWriter writer = new SrgWriter(Files.newBufferedWriter(srgFile))) {
+ try (TinyMappingsReader reader = new TinyMappingsReader(mappings, from, to)) {
+ writer.write(reader.read());
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java
new file mode 100644
index 00000000..4ed3a884
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java
@@ -0,0 +1,144 @@
+/*
+ * 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.util.srg;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.cadixdev.lorenz.MappingSet;
+import org.cadixdev.lorenz.io.srg.tsrg.TSrgWriter;
+import org.cadixdev.lorenz.model.ClassMapping;
+import org.cadixdev.lorenz.model.MethodMapping;
+
+import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.MappingWriter;
+import net.fabricmc.mappingio.adapter.ForwardingMappingVisitor;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+public class Tsrg2Utils {
+ public static void writeTsrg(Consumer<MappingVisitor> visitorConsumer, String dstNamespace, boolean applyParameterMappings, Writer writer)
+ throws IOException {
+ MappingSet set;
+
+ try (MappingsIO2LorenzWriter lorenzWriter = new MappingsIO2LorenzWriter(dstNamespace, applyParameterMappings)) {
+ visitorConsumer.accept(lorenzWriter);
+ set = lorenzWriter.read();
+ }
+
+ try (TSrgWriter w = new TSrgWriter(writer)) {
+ w.write(set);
+ }
+ }
+
+ // TODO Move this elsewhere
+ public abstract static class MappingsIO2Others extends ForwardingMappingVisitor implements MappingWriter {
+ public MappingsIO2Others() {
+ super(new MemoryMappingTree());
+ }
+
+ public MappingTree tree() {
+ return (MappingTree) next;
+ }
+
+ @Override
+ public void close() throws IOException {
+ MappingTree tree = tree();
+ List<String> names = new ArrayList<>();
+
+ for (MappingTree.ClassMapping aClass : tree.getClasses()) {
+ names.add(aClass.getSrcName());
+ }
+
+ for (String name : names) {
+ tree.removeClass(name);
+ }
+ }
+ }
+
+ public static class MappingsIO2LorenzWriter extends MappingsIO2Others {
+ private final Object dstNamespaceUnresolved;
+ private int dstNamespace;
+ private boolean applyParameterMappings;
+
+ public MappingsIO2LorenzWriter(int dstNamespace, boolean applyParameterMappings) {
+ this.dstNamespaceUnresolved = dstNamespace;
+ this.applyParameterMappings = applyParameterMappings;
+ }
+
+ public MappingsIO2LorenzWriter(String dstNamespace, boolean applyParameterMappings) {
+ this.dstNamespaceUnresolved = dstNamespace;
+ this.applyParameterMappings = applyParameterMappings;
+ }
+
+ @Override
+ public void visitNamespaces(String srcNamespace, List<String> dstNamespaces) throws IOException {
+ super.visitNamespaces(srcNamespace, dstNamespaces);
+ this.dstNamespace = dstNamespaceUnresolved instanceof Integer ? (Integer) dstNamespaceUnresolved : dstNamespaces.indexOf((String) dstNamespaceUnresolved);
+ }
+
+ public MappingSet read() throws IOException {
+ return this.read(MappingSet.create());
+ }
+
+ public MappingSet read(final MappingSet mappings) throws IOException {
+ MappingTree tree = tree();
+
+ for (MappingTree.ClassMapping aClass : tree.getClasses()) {
+ ClassMapping<?, ?> lClass = mappings.getOrCreateClassMapping(aClass.getSrcName())
+ .setDeobfuscatedName(aClass.getDstName(dstNamespace));
+
+ for (MappingTree.FieldMapping aField : aClass.getFields()) {
+ String srcDesc = aField.getSrcDesc();
+
+ if (srcDesc == null || srcDesc.isEmpty()) {
+ lClass.getOrCreateFieldMapping(aField.getSrcName())
+ .setDeobfuscatedName(aField.getDstName(dstNamespace));
+ } else {
+ lClass.getOrCreateFieldMapping(aField.getSrcName(), srcDesc)
+ .setDeobfuscatedName(aField.getDstName(dstNamespace));
+ }
+ }
+
+ for (MappingTree.MethodMapping aMethod : aClass.getMethods()) {
+ MethodMapping lMethod = lClass.getOrCreateMethodMapping(aMethod.getSrcName(), aMethod.getSrcDesc())
+ .setDeobfuscatedName(aMethod.getDstName(dstNamespace));
+
+ if (applyParameterMappings) {
+ for (MappingTree.MethodArgMapping aArg : aMethod.getArgs()) {
+ lMethod.getOrCreateParameterMapping(aArg.getLvIndex())
+ .setDeobfuscatedName(aArg.getDstName(dstNamespace));
+ }
+ }
+ }
+ }
+
+ return mappings;
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java
new file mode 100644
index 00000000..7463e447
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java
@@ -0,0 +1,114 @@
+/*
+ * 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.util.srg;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MappingTreeView;
+
+public class Tsrg2Writer {
+ public Tsrg2Writer() {
+ }
+
+ public static String serialize(MappingTree tree) {
+ List<String> namespaces = Stream.concat(Stream.of(tree.getSrcNamespace()), tree.getDstNamespaces().stream()).collect(Collectors.toList());
+ StringBuilder builder = new StringBuilder();
+ writeHeader(namespaces, builder);
+
+ for (MappingTree.ClassMapping classMapping : tree.getClasses()) {
+ writeClass(namespaces, classMapping, builder);
+ }
+
+ return builder.toString();
+ }
+
+ private static void writeClass(List<String> namespaces, MappingTree.ClassMapping def, StringBuilder builder) {
+ writeMapped(null, namespaces, def, builder);
+
+ for (MappingTree.MethodMapping method : def.getMethods()) {
+ writeMethod(namespaces, method, builder);
+ }
+
+ for (MappingTree.FieldMapping field : def.getFields()) {
+ writeMapped('\t', namespaces, field, builder);
+ }
+ }
+
+ private static void writeMethod(List<String> namespaces, MappingTree.MethodMapping def, StringBuilder builder) {
+ writeMapped('\t', namespaces, def, builder);
+
+ for (MappingTree.MethodArgMapping arg : def.getArgs()) {
+ builder.append("\t\t").append(arg.getLvIndex());
+ writeMapped(' ', namespaces, arg, builder);
+ }
+ }
+
+ private static void writeField(List<String> namespaces, MappingTree.FieldMapping def, StringBuilder builder) {
+ writeMapped('\t', namespaces, def, builder);
+ }
+
+ private static void writeMapped(Character first, List<String> namespaces, MappingTreeView.ElementMappingView mapped, StringBuilder builder) {
+ String[] names = namespaces.stream().map(mapped::getName).toArray(String[]::new);
+
+ for (int i = 0; i < names.length; ++i) {
+ String name = names[i];
+
+ if (i == 0) {
+ if (first != null) {
+ builder.append(first);
+ }
+ } else {
+ builder.append(' ');
+ }
+
+ builder.append(name);
+
+ if (i == 0 && mapped instanceof MappingTreeView.MemberMappingView) {
+ String descriptor = ((MappingTreeView.MemberMappingView) mapped).getSrcDesc();
+
+ if (descriptor != null && !descriptor.isEmpty()) {
+ builder.append(' ');
+ builder.append(descriptor);
+ }
+ }
+ }
+
+ builder.append('\n');
+ }
+
+ private static void writeHeader(List<String> namespaces, StringBuilder builder) {
+ builder.append("tsrg2");
+
+ for (String namespace : namespaces) {
+ builder.append(' ');
+ builder.append(namespace);
+ }
+
+ builder.append('\n');
+ }
+}