diff options
Diffstat (limited to 'src/main')
56 files changed, 3674 insertions, 100 deletions
| diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index 4c6557ab..8d7b1a6c 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -27,9 +27,11 @@ package net.fabricmc.loom;  import java.io.File;  import java.nio.file.Path;  import java.util.ArrayList; +import java.util.Arrays;  import java.util.Collection;  import java.util.Collections;  import java.util.HashSet; +import java.util.LinkedHashSet;  import java.util.List;  import java.util.Objects;  import java.util.Set; @@ -49,6 +51,8 @@ import org.gradle.api.artifacts.Dependency;  import org.gradle.api.file.ConfigurableFileCollection;  import org.gradle.api.plugins.BasePluginConvention;  import org.jetbrains.annotations.ApiStatus; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet;  import org.jetbrains.annotations.Nullable;  import net.fabricmc.loom.api.decompilers.LoomDecompiler; @@ -57,11 +61,20 @@ import net.fabricmc.loom.configuration.ide.RunConfigSettings;  import net.fabricmc.loom.configuration.processors.JarProcessor;  import net.fabricmc.loom.configuration.processors.JarProcessorManager;  import net.fabricmc.loom.configuration.providers.MinecraftProvider; +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.MappingsProvider;  import net.fabricmc.loom.configuration.providers.mappings.MojangMappingsDependency;  import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; +import net.fabricmc.loom.util.function.LazyBool;  public class LoomGradleExtension { +	private static final String FORGE_PROPERTY = "loom.forge"; +  	public String refmapName;  	public String loaderLaunchMethod;  	public boolean remapMod = true; @@ -70,20 +83,35 @@ public class LoomGradleExtension {  	public File accessWidener = null;  	public Function<String, Object> intermediaryUrl = mcVer -> "https://maven.fabricmc.net/net/fabricmc/intermediary/" + mcVer + "/intermediary-" + mcVer + "-v2.jar";  	public boolean shareCaches = false; +	@Deprecated +	public String mixinConfig = null; // FORGE: Passed to Minecraft +	public List<String> mixinConfigs = new ArrayList<>(); // FORGE: Passed to Minecraft +	public boolean useFabricMixin = true; // FORGE: Use Fabric Mixin for better refmap resolutions  	private final ConfigurableFileCollection unmappedMods;  	final List<LoomDecompiler> decompilers = new ArrayList<>();  	private final List<JarProcessor> jarProcessors = new ArrayList<>(); +	private boolean silentMojangMappingsLicense = false; +	public Boolean generateSrgTiny = null;  	// Not to be set in the build.gradle  	private final Project project; +	private List<String> dataGenMods = new ArrayList<>();  	private LoomDependencyManager dependencyManager;  	private JarProcessorManager jarProcessorManager;  	private JsonObject installerJson;  	private MappingSet[] srcMappingCache = new MappingSet[2];  	private Mercury[] srcMercuryCache = new Mercury[2]; +	private final LazyBool forge;  	private Set<File> mixinMappings = Collections.synchronizedSet(new HashSet<>()); +	private final List<String> tasksBeforeRun = Collections.synchronizedList(new ArrayList<>()); +	public final List<Supplier<SourceSet>> forgeLocalMods = Collections.synchronizedList(new ArrayList<>(Arrays.asList(new Supplier<SourceSet>() { +		@Override +		public SourceSet get() { +			return project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); +		} +	})));  	private NamedDomainObjectContainer<RunConfigSettings> runs; @@ -114,6 +142,68 @@ public class LoomGradleExtension {  		return srcMercuryCache[id] != null ? srcMercuryCache[id] : (srcMercuryCache[id] = factory.get());  	} +	public void localMods(Action<SourceSetConsumer> action) { +		if (!isForge()) { +			throw new UnsupportedOperationException("Not running with Forge support."); +		} + +		action.execute(new SourceSetConsumer()); +	} + +	public boolean isDataGenEnabled() { +		return isForge() && !dataGenMods.isEmpty(); +	} + +	public List<String> getDataGenMods() { +		return dataGenMods; +	} + +	public class SourceSetConsumer { +		public void add(Object... sourceSets) { +			for (Object sourceSet : sourceSets) { +				if (sourceSet instanceof SourceSet) { +					forgeLocalMods.add(() -> (SourceSet) sourceSet); +				} else { +					forgeLocalMods.add(() -> project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().findByName(String.valueOf(forgeLocalMods))); +				} +			} +		} +	} + +	public void dataGen(Action<DataGenConsumer> action) { +		if (!isForge()) { +			throw new UnsupportedOperationException("Not running with Forge support."); +		} + +		action.execute(new DataGenConsumer()); +	} + +	public class DataGenConsumer { +		public void mod(String... modIds) { +			dataGenMods.addAll(Arrays.asList(modIds)); +		} +	} + +	public void addTaskBeforeRun(String task) { +		this.tasksBeforeRun.add(task); +	} + +	public List<String> getTasksBeforeRun() { +		return tasksBeforeRun; +	} + +	public void mixinConfig(String config) { +		mixinConfigs.add(config); +	} + +	public void silentMojangMappingsLicense() { +		this.silentMojangMappingsLicense = true; +	} + +	public boolean isSilentMojangMappingsLicenseEnabled() { +		return silentMojangMappingsLicense; +	} +  	public Dependency officialMojangMappings() {  		return new MojangMappingsDependency(project, this);  	} @@ -122,6 +212,7 @@ public class LoomGradleExtension {  		this.project = project;  		this.autoGenIDERuns = isRootProject();  		this.unmappedMods = project.files(); +		this.forge = new LazyBool(() -> Boolean.parseBoolean(Objects.toString(project.findProperty(FORGE_PROPERTY))));  		this.runs = project.container(RunConfigSettings.class,  				baseName -> new RunConfigSettings(project, baseName));  	} @@ -266,7 +357,7 @@ public class LoomGradleExtension {  	@Nullable  	private static Dependency findDependency(Project p, Collection<Configuration> configs, BiPredicate<String, String> groupNameFilter) {  		for (Configuration config : configs) { -			for (Dependency dependency : config.getDependencies()) { +			for (Dependency dependency : config.getAllDependencies()) {  				String group = dependency.getGroup();  				String name = dependency.getName(); @@ -300,7 +391,7 @@ public class LoomGradleExtension {  	@Nullable  	private Dependency getMixinDependency() {  		return recurseProjects(p -> { -			List<Configuration> configs = new ArrayList<>(); +			Set<Configuration> configs = new LinkedHashSet<>();  			// check compile classpath first  			Configuration possibleCompileClasspath = p.getConfigurations().findByName("compileClasspath"); @@ -346,6 +437,10 @@ public class LoomGradleExtension {  		return dependencyManager;  	} +	public PatchProvider getPatchProvider() { +		return getDependencyManager().getProvider(PatchProvider.class); +	} +  	public MinecraftProvider getMinecraftProvider() {  		return getDependencyManager().getProvider(MinecraftProvider.class);  	} @@ -358,6 +453,26 @@ public class LoomGradleExtension {  		return getDependencyManager().getProvider(MappingsProvider.class);  	} +	public McpConfigProvider getMcpConfigProvider() { +		return getDependencyManager().getProvider(McpConfigProvider.class); +	} + +	public SrgProvider getSrgProvider() { +		return getDependencyManager().getProvider(SrgProvider.class); +	} + +	public ForgeUniversalProvider getForgeUniversalProvider() { +		return getDependencyManager().getProvider(ForgeUniversalProvider.class); +	} + +	public ForgeUserdevProvider getForgeUserdevProvider() { +		return getDependencyManager().getProvider(ForgeUserdevProvider.class); +	} + +	public ForgeProvider getForgeProvider() { +		return getDependencyManager().getProvider(ForgeProvider.class); +	} +  	public void setDependencyManager(LoomDependencyManager dependencyManager) {  		this.dependencyManager = dependencyManager;  	} @@ -376,7 +491,14 @@ public class LoomGradleExtension {  	public String getRefmapName() {  		if (refmapName == null || refmapName.isEmpty()) { -			String defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-refmap.json"; +			String defaultRefmapName; + +			if (isRootProject()) { +				defaultRefmapName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName() + "-refmap.json"; +			} else { +				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);  			refmapName = defaultRefmapName;  		} @@ -414,6 +536,18 @@ public class LoomGradleExtension {  		return shareCaches;  	} +	public boolean isForge() { +		return forge.getAsBoolean(); +	} + +	public boolean shouldGenerateSrgTiny() { +		if (generateSrgTiny != null) { +			return generateSrgTiny; +		} + +		return isForge(); +	} +  	// Creates a new file each time its called, this is then held onto later when remapping the output jar  	// Required as now when using parallel builds the old single file could be written by another sourceset compile task  	public synchronized File getNextMixinMappings() { diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index cac0b324..b184e925 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -24,6 +24,10 @@  package net.fabricmc.loom; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +  import com.google.common.collect.ImmutableMap;  import com.google.gson.Gson;  import com.google.gson.GsonBuilder; @@ -44,7 +48,14 @@ public class LoomGradlePlugin implements Plugin<Project> {  	@Override  	public void apply(Project project) { -		project.getLogger().lifecycle("Fabric Loom: " + LoomGradlePlugin.class.getPackage().getImplementationVersion()); +		String loomVersion = LoomGradlePlugin.class.getPackage().getImplementationVersion(); +		Set<String> loggedVersions = new HashSet<>(Arrays.asList(System.getProperty("loom.printed.logged", "").split(","))); + +		if (!loggedVersions.contains(loomVersion)) { +			loggedVersions.add(loomVersion); +			System.setProperty("loom.printed.logged", String.join(",", loggedVersions)); +			project.getLogger().lifecycle("Fabric Loom: " + loomVersion); +		}  		refreshDeps = project.getGradle().getStartParameter().isRefreshDependencies(); diff --git a/src/main/java/net/fabricmc/loom/build/JarRemapper.java b/src/main/java/net/fabricmc/loom/build/JarRemapper.java index 17896428..fab51e2d 100644 --- a/src/main/java/net/fabricmc/loom/build/JarRemapper.java +++ b/src/main/java/net/fabricmc/loom/build/JarRemapper.java @@ -37,6 +37,7 @@ import java.util.function.BiFunction;  import org.gradle.api.Action;  import org.objectweb.asm.commons.Remapper; +import net.fabricmc.loom.util.LoggerFilter;  import net.fabricmc.stitch.util.Pair;  import net.fabricmc.tinyremapper.IMappingProvider;  import net.fabricmc.tinyremapper.InputTag; @@ -64,6 +65,7 @@ public class JarRemapper {  	}  	public void remap() throws IOException { +		LoggerFilter.replaceSystemOut();  		TinyRemapper.Builder remapperBuilder = TinyRemapper.newRemapper();  		mappingProviders.forEach(remapperBuilder::withMappings); diff --git a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java index 1abd8662..72cf4620 100644 --- a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java +++ b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java @@ -79,7 +79,7 @@ public class ModCompileRemapper {  				String name = artifact.getModuleVersion().getId().getName();  				String version = artifact.getModuleVersion().getId().getVersion(); -				if (!isFabricMod(logger, artifact)) { +				if (!shouldRemapMod(logger, artifact, extension.isForge(), sourceConfig.getName())) {  					addToRegularCompile(project, regularConfig, artifact);  					continue;  				} @@ -92,7 +92,7 @@ public class ModCompileRemapper {  				modDependencies.add(info); -				String remappedLog = group + ":" + name + ":" + version + (artifact.getClassifier() == null ? "" : ":" + artifact.getClassifier()) + " (" + mappingsSuffix + ")"; +				String remappedLog = group + ":" + name + ":" + version + (artifact.getClassifier() == null ? "" : ":" + artifact.getClassifier()) + " (" + mappingsSuffix + ")" + (info.requiresRemapping() ? " requires remapping" : " already remapped in " + info.getRemappedOutput().getAbsolutePath());  				project.getLogger().info(":providing " + remappedLog);  				File remappedSources = info.getRemappedOutput("sources"); @@ -116,6 +116,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());  			}  		} @@ -124,13 +125,23 @@ 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, ResolvedArtifact artifact) { +	private static boolean shouldRemapMod(Logger logger, ResolvedArtifact artifact, boolean forge, String config) {  		File input = artifact.getFile();  		try (ZipFile zipFile = new ZipFile(input)) { -			if (zipFile.getEntry("fabric.mod.json") != null) { -				logger.info("Found Fabric mod in modCompile: {}", artifact.getId()); +			if (forge) { +				if (zipFile.getEntry("META-INF/mods.toml") != null) { +					logger.info("Found Forge mod in " + config + ": {}", artifact.getId()); +					return true; +				} + +				logger.lifecycle(":could not find forge mod in " + config + " but forcing: {}", artifact.getId());  				return true; +			} else { +				if (zipFile.getEntry("fabric.mod.json") != null) { +					logger.info("Found Fabric mod in " + config + ": {}", artifact.getId()); +					return true; +				}  			}  			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 ecdaf152..02714ab2 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -71,8 +71,9 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {  	private void passMixinArguments(T task) {  		try {  			LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); +			File inMapFile = extension.isForge() ? extension.getMappingsProvider().mixinTinyMappingsWithSrg : extension.getMappingsProvider().tinyMappings;  			Map<String, String> args = new HashMap<String, String>() {{ -					put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, extension.getMappingsProvider().tinyMappings.getCanonicalPath()); +					put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, inMapFile.getCanonicalPath());  					put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, extension.getNextMixinMappings().getCanonicalPath());  					put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, extension));  					put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary"); diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 0f0e6f10..d315f75c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -25,7 +25,11 @@  package net.fabricmc.loom.configuration;  import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import com.google.common.collect.ImmutableMap; +import org.gradle.api.Action;  import org.gradle.api.Project;  import org.gradle.api.Task;  import org.gradle.api.UnknownTaskException; @@ -35,6 +39,7 @@ import org.gradle.api.plugins.JavaPlugin;  import org.gradle.api.plugins.JavaPluginConvention;  import org.gradle.api.tasks.SourceSet;  import org.gradle.api.tasks.bundling.AbstractArchiveTask; +import org.gradle.api.tasks.bundling.Jar;  import org.gradle.api.tasks.javadoc.Javadoc;  import net.fabricmc.loom.LoomGradleExtension; @@ -46,8 +51,15 @@ import net.fabricmc.loom.build.mixin.ScalaApInvoker;  import net.fabricmc.loom.configuration.ide.SetupIntelijRunConfigs;  import net.fabricmc.loom.configuration.providers.LaunchProvider;  import net.fabricmc.loom.configuration.providers.MinecraftProvider; +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.MappingsProvider;  import net.fabricmc.loom.task.AbstractLoomTask; +import net.fabricmc.loom.task.GenVsCodeProjectTask;  import net.fabricmc.loom.task.RemapAllSourcesTask;  import net.fabricmc.loom.task.RemapJarTask;  import net.fabricmc.loom.task.RemapSourcesJarTask; @@ -76,6 +88,36 @@ public final class CompileConfiguration {  		Configuration minecraftConfig = project.getConfigurations().maybeCreate(Constants.Configurations.MINECRAFT);  		minecraftConfig.setTransitive(false); +		project.afterEvaluate(project1 -> { +			if (project.getExtensions().getByType(LoomGradleExtension.class).shouldGenerateSrgTiny()) { +				Configuration srg = project.getConfigurations().maybeCreate(Constants.Configurations.SRG); +				srg.setTransitive(false); +			} + +			if (project.getExtensions().getByType(LoomGradleExtension.class).isDataGenEnabled()) { +				project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main").resources(files -> { +					files.srcDir(project.file("src/generated/resources")); +				}); +			} +		}); + +		if (project.getExtensions().getByType(LoomGradleExtension.class).isForge()) { +			Configuration forgeConfig = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE); +			forgeConfig.setTransitive(false); +			Configuration forgeUserdevConfig = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE_USERDEV); +			forgeUserdevConfig.setTransitive(false); +			Configuration forgeInstallerConfig = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE_INSTALLER); +			forgeInstallerConfig.setTransitive(false); +			Configuration forgeUniversalConfig = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE_UNIVERSAL); +			forgeUniversalConfig.setTransitive(false); +			Configuration forgeDependencies = project.getConfigurations().maybeCreate(Constants.Configurations.FORGE_DEPENDENCIES); +			forgeDependencies.setTransitive(false); +			Configuration mcpConfig = project.getConfigurations().maybeCreate(Constants.Configurations.MCP_CONFIG); +			mcpConfig.setTransitive(false); + +			extendsFrom(Constants.Configurations.MINECRAFT_DEPENDENCIES, Constants.Configurations.FORGE_DEPENDENCIES, project); +		} +  		Configuration includeConfig = project.getConfigurations().maybeCreate(Constants.Configurations.INCLUDE);  		includeConfig.setTransitive(false); // Dont get transitive deps @@ -116,9 +158,15 @@ public final class CompileConfiguration {  	 * @return An object containing the name and the URL of the repository that can be modified later  	 */  	public static MavenArtifactRepository addMavenRepo(Project target, final String name, final String url) { +		return addMavenRepo(target, name, url, repo -> { +		}); +	} + +	public static MavenArtifactRepository addMavenRepo(Project target, final String name, final String url, final Action<MavenArtifactRepository> action) {  		return target.getRepositories().maven(repo -> {  			repo.setName(name);  			repo.setUrl(url); +			action.execute(repo);  		});  	} @@ -153,12 +201,44 @@ public final class CompileConfiguration {  				mavenArtifactRepository.setUrl("https://libraries.minecraft.net/");  			}); +			project1.getRepositories().maven(mavenArtifactRepository -> { +				mavenArtifactRepository.setName("Forge"); +				mavenArtifactRepository.setUrl("https://files.minecraftforge.net/maven/"); + +				mavenArtifactRepository.metadataSources(sources -> { +					sources.mavenPom(); + +					try { +						MavenArtifactRepository.MetadataSources.class.getDeclaredMethod("ignoreGradleMetadataRedirection") +								.invoke(sources); +					} catch (Throwable ignored) { +						// Method not available +					} +				}); +			}); +  			project1.getRepositories().mavenCentral();  			LoomDependencyManager dependencyManager = new LoomDependencyManager();  			extension.setDependencyManager(dependencyManager);  			dependencyManager.addProvider(new MinecraftProvider(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(new MappingsProvider(project));  			dependencyManager.addProvider(new LaunchProvider(project)); @@ -170,6 +250,7 @@ public final class CompileConfiguration {  			if (extension.autoGenIDERuns) {  				SetupIntelijRunConfigs.setup(project1); +				GenVsCodeProjectTask.generate(project1);  			}  			// Enables the default mod remapper @@ -185,6 +266,23 @@ public final class CompileConfiguration {  					remapJarTask.getInput().set(jarTask.getArchivePath());  				} +				if (extension.isForge()) { +					remapJarTask.getToM().set("srg"); +					((Jar) jarTask).manifest(manifest -> { +						List<String> configs = new ArrayList<>(); + +						if (extension.mixinConfig != null) { +							configs.add(extension.mixinConfig); +						} + +						if (extension.mixinConfigs != null) { +							configs.addAll(extension.mixinConfigs); +						} + +						manifest.attributes(ImmutableMap.of("MixinConfigs", String.join(",", configs))); +					}); +				} +  				extension.getUnmappedModCollection().from(jarTask);  				remapJarTask.getAddNestedDependencies().set(true);  				remapJarTask.getRemapAccessWidener().set(true); diff --git a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java index 63ef8354..2b971427 100644 --- a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java @@ -190,7 +190,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)); diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java index 397908c5..28ae3941 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java @@ -137,7 +137,8 @@ public class LoomDependencyManager {  		}  		SourceRemapper sourceRemapper = new SourceRemapper(project, true); -		String mappingsKey = mappingsProvider.getMappingsKey(); +		String platformSuffix = extension.isForge() ? "_forge" : ""; +		String mappingsKey = mappingsProvider.getMappingsKey() + platformSuffix;  		if (extension.getInstallerJson() == null) {  			//If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking @@ -160,7 +161,7 @@ public class LoomDependencyManager {  			}  		} -		if (extension.getInstallerJson() == null) { +		if (extension.getInstallerJson() == null && !extension.isForge()) {  			project.getLogger().warn("fabric-installer.json not found in classpath!");  		} diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java index 936e6b7a..ae73c145 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java @@ -55,6 +55,7 @@ import net.fabricmc.accesswidener.AccessWidenerVisitor;  import net.fabricmc.accesswidener.AccessWidenerWriter;  import net.fabricmc.loom.LoomGradleExtension;  import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;  import net.fabricmc.loom.util.Checksum;  import net.fabricmc.loom.util.Constants;  import net.fabricmc.tinyremapper.TinyRemapper; @@ -94,8 +95,9 @@ public class AccessWidenerJarProcessor implements JarProcessor {  					throw new UnsupportedOperationException(String.format("Access Widener namespace '%s' is not a valid namespace, it must be one of: '%s'", accessWidener.getNamespace(), String.join(", ", validNamespaces)));  				} -				TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper("official", "named"); -				tinyRemapper.readClassPath(loomGradleExtension.getMinecraftMappedProvider().getRemapClasspath()); +				TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper(null, "official", "named"); +				loomGradleExtension.getMinecraftMappedProvider(); +				tinyRemapper.readClassPath(MinecraftMappedProvider.getRemapClasspath(project));  				AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, tinyRemapper.getRemapper(), "named");  				accessWidener = remapper.remap(); diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java index 5acf4e78..eb85934a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -24,11 +24,19 @@  package net.fabricmc.loom.configuration.ide; +import java.io.File;  import java.io.IOException;  import java.io.InputStream;  import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap;  import java.util.List;  import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport;  import com.google.common.base.Strings;  import com.google.common.collect.ImmutableList; @@ -51,11 +59,14 @@ public class RunConfig {  	public String configName;  	public String eclipseProjectName;  	public String ideaModuleName; +	public String vscodeProjectName;  	public String mainClass;  	public String runDirIdeaUrl;  	public String runDir;  	public String vmArgs;  	public String programArgs; +	public List<String> vscodeBeforeRun = new ArrayList<>(); +	public final Map<String, String> envVariables = new HashMap<>();  	public Element genRuns(Element doc) {  		Element root = this.addXml(doc, "component", ImmutableMap.of("name", "ProjectRunConfigurationManager")); @@ -73,6 +84,14 @@ public class RunConfig {  			this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", programArgs));  		} +		if (!envVariables.isEmpty()) { +			Element envs = this.addXml(root, "envs", ImmutableMap.of()); + +			for (Map.Entry<String, String> envEntry : envVariables.entrySet()) { +				this.addXml(envs, "env", ImmutableMap.of("name", envEntry.getKey(), "value", envEntry.getValue())); +			} +		} +  		return root;  	} @@ -106,6 +125,7 @@ public class RunConfig {  	private static void populate(Project project, LoomGradleExtension extension, RunConfig runConfig, String mode) {  		runConfig.configName += extension.isRootProject() ? "" : " (" + project.getPath() + ")";  		runConfig.eclipseProjectName = project.getExtensions().getByType(EclipseModel.class).getProject().getName(); +		runConfig.vscodeProjectName = extension.isRootProject() ? "" : project.getPath();  		runConfig.vmArgs = "";  		runConfig.programArgs = ""; @@ -117,6 +137,23 @@ public class RunConfig {  			runConfig.vmArgs = "-Dfabric.dli.config=" + encodeEscaped(extension.getDevLauncherConfig().getAbsolutePath()) + " -Dfabric.dli.env=" + mode.toLowerCase();  		} +		if (extension.isForge()) { +			List<String> modClasses = new ArrayList<>(); + +			for (Supplier<SourceSet> sourceSetSupplier : extension.forgeLocalMods) { +				SourceSet sourceSet = sourceSetSupplier.get(); +				String sourceSetName = sourceSet.getName() + "_" + UUID.randomUUID().toString().replace("-", "").substring(0, 7); + +				Stream.concat( +						Stream.of(sourceSet.getOutput().getResourcesDir().getAbsolutePath()), +						StreamSupport.stream(sourceSet.getOutput().getClassesDirs().spliterator(), false) +								.map(File::getAbsolutePath) +				).map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses)); +			} + +			runConfig.envVariables.put("MOD_CLASSES", String.join(File.pathSeparator, modClasses)); +		} +  		if (extension.getLoaderLaunchMethod().equals("launchwrapper")) {  			// if installer.json found...  			JsonObject installerJson = extension.getInstallerJson(); @@ -232,6 +269,25 @@ public class RunConfig {  		dummyConfig = dummyConfig.replace("%PROGRAM_ARGS%", programArgs.replaceAll("\"", """));  		dummyConfig = dummyConfig.replace("%VM_ARGS%", vmArgs.replaceAll("\"", """)); +		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("\"", """)); +				builder.append("\" value=\""); +				builder.append(env.getValue().replaceAll("\"", """)); +				builder.append("\"/>"); +			} + +			builder.append("</envs>"); +			envs = builder.toString(); +		} + +		dummyConfig = dummyConfig.replace("%ENVS%", envs); +  		return dummyConfig;  	} 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 e28c4e62..1a3ff178 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java @@ -256,7 +256,7 @@ public final class RunConfigSettings implements Named {  	public void client() {  		startFirstThread();  		mode("client"); -		defaultMainClass(Constants.Knot.KNOT_CLIENT); +		defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_CLIENT);  	}  	/** @@ -265,7 +265,15 @@ public final class RunConfigSettings implements Named {  	public void server() {  		programArg("nogui");  		mode("server"); -		defaultMainClass(Constants.Knot.KNOT_SERVER); +		defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER); +	} + +	/** +	 * Configure run config with the default server options. +	 */ +	public void data() { +		mode("data"); +		defaultMainClass(getExtension().isForge() ? Constants.ForgeUserDev.LAUNCH_TESTING : Constants.Knot.KNOT_SERVER);  	}  	/** 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 367a49a6..14f14e88 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -35,9 +35,13 @@ import java.nio.charset.StandardCharsets;  import java.nio.file.Path;  import java.util.ArrayList;  import java.util.HashMap; +import java.util.Iterator;  import java.util.List; +import java.util.Locale;  import java.util.Map; +import java.util.jar.Attributes;  import java.util.jar.JarFile; +import java.util.jar.Manifest;  import java.util.stream.Collectors;  import java.util.zip.ZipEntry; @@ -60,7 +64,11 @@ import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo;  import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;  import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;  import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.LoggerFilter;  import net.fabricmc.loom.util.TinyRemapperMappingsHelper; +import net.fabricmc.loom.util.srg.AtRemapper; +import net.fabricmc.loom.util.srg.CoreModClassRemapper; +import net.fabricmc.mapping.tree.TinyTree;  import net.fabricmc.tinyremapper.InputTag;  import net.fabricmc.tinyremapper.OutputConsumerPath;  import net.fabricmc.tinyremapper.TinyRemapper; @@ -97,6 +105,7 @@ public class ModProcessor {  	}  	private static void stripNestedJars(File file) { +		if (!ZipUtil.containsEntry(file, "fabric.mod.json")) return;  		// Strip out all contained jar info as we dont want loader to try and load the jars contained in dev.  		ZipUtil.transformEntries(file, new ZipEntryTransformerEntry[] {(new ZipEntryTransformerEntry("fabric.mod.json", new StringZipEntryTransformer() {  			@Override @@ -129,24 +138,26 @@ public class ModProcessor {  	private static void remapJars(Project project, List<ModDependencyInfo> processList) throws IOException {  		LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); -		String fromM = "intermediary"; +		String fromM = extension.isForge() ? "srg" : "intermediary";  		String toM = "named";  		MinecraftMappedProvider mappedProvider = extension.getMinecraftMappedProvider();  		MappingsProvider mappingsProvider = extension.getMappingsProvider(); -		Path mc = mappedProvider.getIntermediaryJar().toPath(); +		Path mc = extension.isForge() ? mappedProvider.getSrgJar().toPath() : mappedProvider.getIntermediaryJar().toPath();  		Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles() -							.stream().map(File::toPath).toArray(Path[]::new); +				.stream().map(File::toPath).toArray(Path[]::new);  		List<ModDependencyInfo> remapList = processList.stream().filter(ModDependencyInfo::requiresRemapping).collect(Collectors.toList());  		project.getLogger().lifecycle(":remapping " + remapList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ")"); +		TinyTree mappings = extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(); +		LoggerFilter.replaceSystemOut();  		TinyRemapper remapper = TinyRemapper.newRemapper() -						.withMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false)) -						.renameInvalidLocals(false) -						.build(); +				.withMappings(TinyRemapperMappingsHelper.create(mappings, fromM, toM, false)) +				.renameInvalidLocals(false) +				.build();  		remapper.readClassPathAsync(mc);  		remapper.readClassPathAsync(mcDeps); @@ -198,10 +209,56 @@ public class ModProcessor {  				ZipUtil.replaceEntry(info.getRemappedOutput(), info.getAccessWidener(), accessWidener);  			} +			if (extension.isForge()) { +				AtRemapper.remap(project.getLogger(), info.getRemappedOutput().toPath(), mappings); +				CoreModClassRemapper.remapJar(info.getRemappedOutput().toPath(), mappings, project.getLogger()); + +				if (ZipUtil.containsEntry(info.getRemappedOutput(), "META-INF/MANIFEST.MF")) { +					ZipUtil.transformEntry(info.getRemappedOutput(), "META-INF/MANIFEST.MF", (in, zipEntry, out) -> { +						Manifest manifest = new Manifest(in); +						fixManifest(manifest); +						out.putNextEntry(new ZipEntry(zipEntry.getName())); +						manifest.write(out); +						out.closeEntry(); +					}); +				} + +				List<String> filesToRemove = new ArrayList<>(); +				ZipUtil.iterate(info.getRemappedOutput(), (in, zipEntry) -> { +					if (zipEntry.getName().toLowerCase(Locale.ROOT).endsWith(".rsa") || zipEntry.getName().toLowerCase(Locale.ROOT).endsWith(".sf")) { +						if (zipEntry.getName().startsWith("META-INF")) { +							filesToRemove.add(zipEntry.getName()); +						} +					} +				}); +				ZipUtil.removeEntries(info.getRemappedOutput(), filesToRemove.toArray(new String[0])); +			} +  			info.finaliseRemapping();  		}  	} +	private static void fixManifest(Manifest manifest) { +		Attributes mainAttrs = manifest.getMainAttributes(); + +		mainAttrs.remove(Attributes.Name.SIGNATURE_VERSION); + +		for (Iterator<Attributes> it = manifest.getEntries().values().iterator(); it.hasNext(); ) { +			Attributes attrs = it.next(); + +			for (Iterator<Object> it2 = attrs.keySet().iterator(); it2.hasNext(); ) { +				Attributes.Name attrName = (Attributes.Name) it2.next(); +				String name = attrName.toString(); + +				if (name.endsWith("-Digest") || name.contains("-Digest-") || name.equals("Magic")) { +					it2.remove(); +				} +			} + +			if (attrs.isEmpty()) it.remove(); +		} +	} +  	public static JsonObject readInstallerJson(File file, Project project) {  		try {  			LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); 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 38547817..9c366225 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java @@ -50,7 +50,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().lifecycle(":processing mapped jar");  			invalidateJars(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java index 07b8c04e..4ecb34fb 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java @@ -68,6 +68,53 @@ public class LaunchProvider extends DependencyProvider {  				.argument("client", "--assetsDir")  				.argument("client", new File(getExtension().getUserCache(), "assets").getAbsolutePath()); +		if (getExtension().isForge()) { +			launchConfig +					.property("fabric.yarnWithSrg.path", getExtension().getMappingsProvider().tinyMappingsWithSrg.toAbsolutePath().toString()) + +					.argument("--fml.mcVersion") +					.argument(getExtension().getMinecraftProvider().getMinecraftVersion()) +					.argument("--fml.forgeVersion") +					.argument(getExtension().getForgeProvider().getVersion().getForgeVersion()) + +					.argument("client", "--launchTarget") +					.argument("client", "fmluserdevclient") + +					.argument("server", "--launchTarget") +					.argument("server", "fmluserdevserver") + +					.argument("data", "--launchTarget") +					.argument("data", "fmluserdevdata") +					.argument("data", "--all") +					.argument("data", "--mod") +					.argument("data", String.join(",", getExtension().getDataGenMods())) +					.argument("data", "--output") +					.argument("data", getProject().file("src/generated/resources").getAbsolutePath()) + +					.property("mixin.env.remapRefMap", "true"); + +			if (getExtension().useFabricMixin) { +				launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingsProvider().mixinTinyMappingsWithSrg.getAbsolutePath()); +			} else { +				launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.getAbsolutePath()); +			} + +			String mixinConfig = getExtension().mixinConfig; +			List<String> mixinConfigs = getExtension().mixinConfigs; + +			if (mixinConfig != null) { +				launchConfig.argument("-mixin.config"); +				launchConfig.argument(mixinConfig); +			} + +			if (mixinConfigs != null) { +				for (String config : mixinConfigs) { +					launchConfig.argument("-mixin.config"); +					launchConfig.argument(config); +				} +			} +		} +  		//Enable ansi by default for idea and vscode  		if (new File(getProject().getRootDir(), ".vscode").exists()  				|| new File(getProject().getRootDir(), ".idea").exists() @@ -82,6 +129,10 @@ public class LaunchProvider extends DependencyProvider {  		addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, "runtimeOnly");  		annotationDependency = addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, "compileOnly"); +		if (getExtension().isForge()) { +			addDependency(Constants.Dependencies.JAVAX_ANNOTATIONS + Constants.Dependencies.Versions.JAVAX_ANNOTATIONS, "compileOnly"); +		} +  		postPopulationScheduler.accept(this::writeRemapClassPath);  	} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java index bbb6d196..8d0a8d5e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java @@ -34,6 +34,7 @@ import java.util.function.Consumer;  import java.util.zip.ZipError;  import com.google.common.io.Files; +import com.google.gson.Gson;  import com.google.gson.GsonBuilder;  import org.gradle.api.GradleException;  import org.gradle.api.Project; @@ -56,10 +57,13 @@ public class MinecraftProvider extends DependencyProvider {  	private MinecraftLibraryProvider libraryProvider;  	private File minecraftJson; -	private File minecraftClientJar; -	private File minecraftServerJar; +	public File minecraftClientJar; +	public File minecraftServerJar;  	private File minecraftMergedJar;  	private File versionManifestJson; +	private String jarSuffix = ""; + +	Gson gson = new Gson();  	public MinecraftProvider(Project project) {  		super(project); @@ -69,6 +73,10 @@ public class MinecraftProvider extends DependencyProvider {  	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(); @@ -241,6 +249,14 @@ public class MinecraftProvider extends DependencyProvider {  		return libraryProvider;  	} +	public String getJarSuffix() { +		return jarSuffix; +	} + +	public void setJarSuffix(String jarSuffix) { +		this.jarSuffix = jarSuffix; +	} +  	@Override  	public String getTargetConfig() {  		return Constants.Configurations.MINECRAFT; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java new file mode 100644 index 00000000..d1e0d462 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java @@ -0,0 +1,87 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.util.function.Consumer; + +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; + +public class ForgeProvider extends DependencyProvider { +	private ForgeVersion version = new ForgeVersion(null); + +	public ForgeProvider(Project project) { +		super(project); +	} + +	@Override +	public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { +		version = new ForgeVersion(dependency.getDependency().getVersion()); +		addDependency(dependency.getDepString() + ":userdev", Constants.Configurations.FORGE_USERDEV); +		addDependency(dependency.getDepString() + ":installer", Constants.Configurations.FORGE_INSTALLER); +	} + +	public ForgeVersion getVersion() { +		return version; +	} + +	@Override +	public String getTargetConfig() { +		return Constants.Configurations.FORGE; +	} + +	public static final class ForgeVersion { +		private final String minecraftVersion; +		private final String forgeVersion; + +		public ForgeVersion(String combined) { +			if (combined == null) { +				this.minecraftVersion = "NO_VERSION"; +				this.forgeVersion = "NO_VERSION"; +				return; +			} + +			int hyphenIndex = combined.indexOf('-'); + +			if (hyphenIndex != -1) { +				this.minecraftVersion = combined.substring(0, hyphenIndex); +				this.forgeVersion = combined.substring(hyphenIndex + 1); +			} else { +				this.minecraftVersion = "NO_VERSION"; +				this.forgeVersion = combined; +			} +		} + +		public String getMinecraftVersion() { +			return minecraftVersion; +		} + +		public String getForgeVersion() { +			return forgeVersion; +		} +	} +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java new file mode 100644 index 00000000..f0c9ba0c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUniversalProvider.java @@ -0,0 +1,72 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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; +import net.fabricmc.loom.util.JarUtil; + +public class ForgeUniversalProvider extends DependencyProvider { +	private File forge; +	private File forgeManifest; + +	public ForgeUniversalProvider(Project project) { +		super(project); +	} + +	@Override +	public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { +		forge = new File(getExtension().getProjectPersistentCache(), "forge-" + dependency.getDependency().getVersion() + "-universal.jar"); +		forgeManifest = new File(getExtension().getProjectPersistentCache(), "forge-" + dependency.getDependency().getVersion() + "-manifest.mf"); + +		if (!forge.exists() || isRefreshDeps()) { +			File dep = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge")); +			FileUtils.copyFile(dep, forge); +		} + +		if (!forgeManifest.exists() || isRefreshDeps()) { +			JarUtil.extractFile(forge, "META-INF/MANIFEST.MF", forgeManifest); +		} +	} + +	public File getForge() { +		return forge; +	} + +	public File getForgeManifest() { +		return forgeManifest; +	} + +	@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..de0394ce --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -0,0 +1,105 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.File; +import java.io.Reader; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; + +public class ForgeUserdevProvider extends DependencyProvider { +	private File userdevJar; + +	public ForgeUserdevProvider(Project project) { +		super(project); +	} + +	@Override +	public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { +		userdevJar = new File(getExtension().getProjectPersistentCache(), "forge-" + dependency.getDependency().getVersion() + "-userdev.jar"); + +		Path configJson = getExtension() +				.getProjectPersistentCache() +				.toPath() +				.resolve("forge-config-" + dependency.getDependency().getVersion() + ".json"); + +		if (!userdevJar.exists() || Files.notExists(configJson) || isRefreshDeps()) { +			File resolved = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge userdev")); +			Files.copy(resolved.toPath(), userdevJar.toPath(), StandardCopyOption.REPLACE_EXISTING); + +			try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + resolved.toURI()), ImmutableMap.of("create", false))) { +				Files.copy(fs.getPath("config.json"), configJson, StandardCopyOption.REPLACE_EXISTING); +			} +		} + +		JsonObject json; + +		try (Reader reader = Files.newBufferedReader(configJson)) { +			json = new Gson().fromJson(reader, JsonObject.class); +		} + +		addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); +		addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); +		addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); + +		for (JsonElement lib : json.get("libraries").getAsJsonArray()) { +			if (lib.getAsString().startsWith("org.spongepowered:mixin:")) { +				if (getExtension().useFabricMixin) { +					addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES); +					continue; +				} +			} + +			addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); +		} + +		// TODO: Read launch configs from the JSON too +		// TODO: Should I copy the patches from here as well? +		//       That'd require me to run the "MCP environment" fully up to merging. +	} + +	public File getUserdevJar() { +		return userdevJar; +	} + +	@Override +	public String getTargetConfig() { +		return Constants.Configurations.FORGE_USERDEV; +	} +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java new file mode 100644 index 00000000..037f4710 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -0,0 +1,72 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; + +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; + +public class McpConfigProvider extends DependencyProvider { +	private File mcp; + +	public McpConfigProvider(Project project) { +		super(project); +	} + +	@Override +	public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { +		init(dependency.getDependency().getVersion()); + +		if (mcp.exists() && !isRefreshDeps()) { +			return; // No work for us to do here +		} + +		Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath(); + +		if (!mcp.exists() || isRefreshDeps()) { +			Files.copy(mcpZip, mcp.toPath(), StandardCopyOption.REPLACE_EXISTING); +		} +	} + +	private void init(String version) { +		mcp = new File(getExtension().getUserCache(), "mcp-" + version + ".zip"); +	} + +	public File getMcp() { +		return mcp; +	} + +	@Override +	public String getTargetConfig() { +		return Constants.Configurations.MCP_CONFIG; +	} +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java new file mode 100644 index 00000000..2afd3eed --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -0,0 +1,584 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.lang.reflect.Field; +import java.net.URI; +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.Path; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonParser; +import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer; +import net.minecraftforge.accesstransformer.AccessTransformerEngine; +import net.minecraftforge.accesstransformer.TransformerProcessor; +import net.minecraftforge.accesstransformer.parser.AccessTransformerList; +import net.minecraftforge.binarypatcher.ConsoleTool; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.zeroturnaround.zip.ZipUtil; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.providers.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; +import net.fabricmc.loom.util.Checksum; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DownloadUtil; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.JarUtil; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.TinyRemapperMappingsHelper; +import net.fabricmc.loom.util.function.FsPathConsumer; +import net.fabricmc.loom.util.srg.InnerClassRemapper; +import net.fabricmc.loom.util.srg.SpecialSourceExecutor; +import net.fabricmc.mapping.tree.TinyTree; +import net.fabricmc.tinyremapper.OutputConsumerPath; +import net.fabricmc.tinyremapper.TinyRemapper; + +public class MinecraftPatchedProvider extends DependencyProvider { +	private final MappingsProvider mappingsProvider; +	// Step 1: Remap Minecraft to SRG +	private File minecraftClientSrgJar; +	private File minecraftServerSrgJar; +	// Step 2: Binary Patch +	private File minecraftClientPatchedSrgJar; +	private File minecraftServerPatchedSrgJar; +	// Step 3: Access Transform +	private File minecraftClientPatchedSrgATJar; +	private File minecraftServerPatchedSrgATJar; +	// Step 4: Remap Patched AT to Official +	private File minecraftClientPatchedOfficialJar; +	private File minecraftServerPatchedOfficialJar; +	// Step 5: Merge +	private File minecraftMergedPatchedJar; +	private File projectAtHash; +	@Nullable +	private File projectAt = null; +	private boolean atDirty = false; + +	public MinecraftPatchedProvider(MappingsProvider mappingsProvider, Project project) { +		super(project); +		this.mappingsProvider = mappingsProvider; +	} + +	public void initFiles() throws IOException { +		projectAtHash = new File(getExtension().getProjectPersistentCache(), "at.sha256"); + +		SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); + +		for (File srcDir : main.getResources().getSrcDirs()) { +			File projectAt = new File(srcDir, "META-INF/accesstransformer.cfg"); + +			if (projectAt.exists()) { +				this.projectAt = projectAt; +				break; +			} +		} + +		if (isRefreshDeps() || !projectAtHash.exists()) { +			writeAtHash(); +			atDirty = projectAt != null; +		} else { +			byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read(); +			byte[] current = projectAt != null ? Checksum.sha256(projectAt) : Checksum.sha256(""); +			boolean mismatched = !Arrays.equals(current, expected); + +			if (mismatched) { +				writeAtHash(); +			} + +			atDirty = mismatched; +		} + +		MinecraftProvider minecraftProvider = getExtension().getMinecraftProvider(); +		PatchProvider patchProvider = getExtension().getPatchProvider(); +		String minecraftVersion = minecraftProvider.getMinecraftVersion(); +		String jarSuffix = "-patched-forge-" + patchProvider.forgeVersion; + +		if (getExtension().useFabricMixin) { +			jarSuffix += "-fabric-mixin"; +		} + +		minecraftProvider.setJarSuffix(jarSuffix); + +		File globalCache = getExtension().getUserCache(); +		File cache = usesProjectCache() ? getExtension().getProjectPersistentCache() : globalCache; + +		minecraftClientSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-srg.jar"); +		minecraftServerSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-srg.jar"); +		minecraftClientPatchedSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-client-srg" + jarSuffix + ".jar"); +		minecraftServerPatchedSrgJar = new File(globalCache, "minecraft-" + minecraftVersion + "-server-srg" + jarSuffix + ".jar"); +		minecraftClientPatchedSrgATJar = new File(cache, "minecraft-" + minecraftVersion + "-client-srg-at" + jarSuffix + ".jar"); +		minecraftServerPatchedSrgATJar = new File(cache, "minecraft-" + minecraftVersion + "-server-srg-at" + jarSuffix + ".jar"); +		minecraftClientPatchedOfficialJar = new File(cache, "minecraft-" + minecraftVersion + "-client" + jarSuffix + ".jar"); +		minecraftServerPatchedOfficialJar = new File(cache, "minecraft-" + minecraftVersion + "-server" + jarSuffix + ".jar"); +		minecraftMergedPatchedJar = new File(cache, "minecraft-" + minecraftVersion + "-merged" + jarSuffix + ".jar"); + +		if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(Predicates.not(File::exists))) { +			cleanAllCache(); +		} else if (atDirty || Stream.of(getProjectCache()).anyMatch(Predicates.not(File::exists))) { +			cleanProjectCache(); +		} +	} + +	public void cleanAllCache() { +		for (File file : getGlobalCaches()) { +			file.delete(); +		} + +		cleanProjectCache(); +	} + +	private File[] getGlobalCaches() { +		return new File[] { +				minecraftClientSrgJar, +				minecraftServerSrgJar, +				minecraftClientPatchedSrgJar, +				minecraftServerPatchedSrgJar +		}; +	} + +	public void cleanProjectCache() { +		for (File file : getProjectCache()) { +			file.delete(); +		} +	} + +	private File[] getProjectCache() { +		return new File[] { +				minecraftClientPatchedSrgATJar, +				minecraftServerPatchedSrgATJar, +				minecraftClientPatchedOfficialJar, +				minecraftServerPatchedOfficialJar, +				minecraftMergedPatchedJar +		}; +	} + +	@Override +	public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { +		initFiles(); + +		if (atDirty) { +			getProject().getLogger().lifecycle(":found dirty access transformers"); +		} + +		boolean dirty = false; + +		if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { +			dirty = true; +			// Remap official jars to MCPConfig remapped srg jars +			createSrgJars(getProject().getLogger()); +		} + +		if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) { +			dirty = true; +			patchJars(getProject().getLogger()); +			injectForgeClasses(getProject().getLogger()); +		} + +		if (atDirty || !minecraftClientPatchedSrgATJar.exists() || !minecraftServerPatchedSrgATJar.exists()) { +			dirty = true; +			accessTransformForge(getProject().getLogger()); +		} + +		if (dirty) { +			remapPatchedJars(getProject().getLogger()); +		} + +		if (dirty || !minecraftMergedPatchedJar.exists()) { +			mergeJars(getProject().getLogger()); +		} +	} + +	private void writeAtHash() throws IOException { +		try (FileOutputStream out = new FileOutputStream(projectAtHash)) { +			if (projectAt != null) { +				out.write(Checksum.sha256(projectAt)); +			} else { +				out.write(Checksum.sha256("")); +			} +		} +	} + +	private void createSrgJars(Logger logger) throws Exception { +		McpConfigProvider mcpProvider = getExtension().getMcpConfigProvider(); + +		MinecraftProvider minecraftProvider = getExtension().getMinecraftProvider(); + +		String[] mappingsPath = {null}; + +		if (!ZipUtil.handle(mcpProvider.getMcp(), "config.json", (in, zipEntry) -> { +			mappingsPath[0] = new JsonParser().parse(new InputStreamReader(in)).getAsJsonObject().get("data").getAsJsonObject().get("mappings").getAsString(); +		})) { +			throw new IllegalStateException("Failed to find 'config.json' in " + mcpProvider.getMcp().getAbsolutePath() + "!"); +		} + +		Path[] tmpSrg = {null}; + +		if (!ZipUtil.handle(mcpProvider.getMcp(), mappingsPath[0], (in, zipEntry) -> { +			tmpSrg[0] = Files.createTempFile(null, null); + +			try (BufferedWriter writer = Files.newBufferedWriter(tmpSrg[0])) { +				IOUtils.copy(in, writer, StandardCharsets.UTF_8); +			} +		})) { +			throw new IllegalStateException("Failed to find mappings '" + mappingsPath[0] + "' in " + mcpProvider.getMcp().getAbsolutePath() + "!"); +		} + +		File specialSourceJar = new File(getExtension().getUserCache(), "SpecialSource-1.8.3-shaded.jar"); +		DownloadUtil.downloadIfChanged(new URL("https://repo1.maven.org/maven2/net/md-5/SpecialSource/1.8.3/SpecialSource-1.8.3-shaded.jar"), specialSourceJar, getProject().getLogger(), true); + +		ThreadingUtils.run(() -> { +			Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), mappingsProvider, "client", specialSourceJar, minecraftProvider.minecraftClientJar.toPath(), tmpSrg[0]), minecraftClientSrgJar.toPath()); +		}, () -> { +				Files.copy(SpecialSourceExecutor.produceSrgJar(getProject(), mappingsProvider, "server", specialSourceJar, minecraftProvider.minecraftServerJar.toPath(), tmpSrg[0]), minecraftServerSrgJar.toPath()); +			}); +	} + +	private void fixParameterAnnotation(File jarFile) throws Exception { +		getProject().getLogger().info(":fixing parameter annotations for " + jarFile.toString()); + +		try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { +			for (Path rootDir : fs.getRootDirectories()) { +				for (Path file : (Iterable<? extends Path>) Files.walk(rootDir)::iterator) { +					if (!file.toString().endsWith(".class")) continue; +					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); +					} +				} +			} +		} +	} + +	private void injectForgeClasses(Logger logger) throws IOException { +		logger.lifecycle(":injecting forge classes into minecraft"); +		ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> { +			copyAll(getExtension().getForgeUniversalProvider().getForge(), environment.patchedSrgJar.apply(this)); +			copyUserdevFiles(getExtension().getForgeUserdevProvider().getUserdevJar(), environment.patchedSrgJar.apply(this)); +		}); + +		logger.lifecycle(":injecting loom classes into minecraft"); +		File injection = File.createTempFile("loom-injection", ".jar"); + +		try (InputStream in = MinecraftProvider.class.getResourceAsStream("/inject/injection.jar")) { +			FileUtils.copyInputStreamToFile(in, injection); +		} + +		for (Environment environment : Environment.values()) { +			String side = environment.side(); +			File target = environment.patchedSrgJar.apply(this); +			walkFileSystems(injection, target, it -> { +				if (it.getFileName().toString().equals("MANIFEST.MF")) { +					return false; +				} + +				return getExtension().useFabricMixin || !it.getFileName().toString().endsWith("cpw.mods.modlauncher.api.ITransformationService"); +			}, this::copyReplacing); +		} +	} + +	private void accessTransformForge(Logger logger) throws Exception { +		for (Environment environment : Environment.values()) { +			String side = environment.side(); +			logger.lifecycle(":access transforming minecraft (" + side + ")"); + +			File input = environment.patchedSrgJar.apply(this); +			File inputCopied = File.createTempFile("at" + side, ".jar"); +			FileUtils.copyFile(input, inputCopied); +			File target = environment.patchedSrgATJar.apply(this); +			target.delete(); +			File at = File.createTempFile("at" + side, ".cfg"); +			JarUtil.extractFile(inputCopied, "META-INF/accesstransformer.cfg", at); +			String[] args = new String[] { +					"--inJar", inputCopied.getAbsolutePath(), +					"--outJar", target.getAbsolutePath(), +					"--atFile", at.getAbsolutePath() +			}; + +			if (usesProjectCache()) { +				args = Arrays.copyOf(args, args.length + 2); +				args[args.length - 2] = "--atFile"; +				args[args.length - 1] = projectAt.getAbsolutePath(); +			} + +			resetAccessTransformerEngine(); +			TransformerProcessor.main(args); +			inputCopied.delete(); +		} +	} + +	private void resetAccessTransformerEngine() throws Exception { +		// Thank you Forge, I love you +		Field field = AccessTransformerEngine.class.getDeclaredField("masterList"); +		field.setAccessible(true); +		AccessTransformerList list = (AccessTransformerList) field.get(AccessTransformerEngine.INSTANCE); +		field = AccessTransformerList.class.getDeclaredField("accessTransformers"); +		field.setAccessible(true); +		((Map<?, ?>) field.get(list)).clear(); +	} + +	private enum Environment { +		CLIENT(provider -> provider.minecraftClientSrgJar, +				provider -> provider.minecraftClientPatchedSrgJar, +				provider -> provider.minecraftClientPatchedSrgATJar, +				provider -> provider.minecraftClientPatchedOfficialJar +		), +		SERVER(provider -> provider.minecraftServerSrgJar, +				provider -> provider.minecraftServerPatchedSrgJar, +				provider -> provider.minecraftServerPatchedSrgATJar, +				provider -> provider.minecraftServerPatchedOfficialJar +		); + +		final Function<MinecraftPatchedProvider, File> srgJar; +		final Function<MinecraftPatchedProvider, File> patchedSrgJar; +		final Function<MinecraftPatchedProvider, File> patchedSrgATJar; +		final Function<MinecraftPatchedProvider, File> patchedOfficialJar; + +		Environment(Function<MinecraftPatchedProvider, File> srgJar, +				Function<MinecraftPatchedProvider, File> patchedSrgJar, +				Function<MinecraftPatchedProvider, File> patchedSrgATJar, +				Function<MinecraftPatchedProvider, File> patchedOfficialJar) { +			this.srgJar = srgJar; +			this.patchedSrgJar = patchedSrgJar; +			this.patchedSrgATJar = patchedSrgATJar; +			this.patchedOfficialJar = patchedOfficialJar; +		} + +		public String side() { +			return name().toLowerCase(Locale.ROOT); +		} +	} + +	private void remapPatchedJars(Logger logger) throws Exception { +		Path[] libraries = MinecraftMappedProvider.getRemapClasspath(getProject()); + +		ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> { +			logger.lifecycle(":remapping minecraft (TinyRemapper, " + environment.side() + ", srg -> official)"); +			TinyTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); + +			Path input = environment.patchedSrgATJar.apply(this).toPath(); +			Path output = environment.patchedOfficialJar.apply(this).toPath(); + +			Files.deleteIfExists(output); + +			TinyRemapper remapper = TinyRemapper.newRemapper() +					.withMappings(TinyRemapperMappingsHelper.create(mappingsWithSrg, "srg", "official", true)) +					.withMappings(InnerClassRemapper.of(input, mappingsWithSrg, "srg", "official")) +					.renameInvalidLocals(true) +					.rebuildSourceFilenames(true) +					.fixPackageAccess(true) +					.build(); + +			try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) { +				outputConsumer.addNonClassFiles(input); + +				remapper.readClassPath(libraries); +				remapper.readInputs(input); +				remapper.apply(outputConsumer); +			} finally { +				remapper.finish(); +			} +		}); +	} + +	private void patchJars(Logger logger) throws IOException { +		logger.lifecycle(":patching jars"); + +		PatchProvider patchProvider = getExtension().getPatchProvider(); +		patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches); +		patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); + +		ThreadingUtils.run(Arrays.asList(Environment.values()), environment -> { +			copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); +			fixParameterAnnotation(environment.patchedSrgJar.apply(this)); +		}); +	} + +	private void patchJars(File clean, File output, Path patches) throws IOException { +		PrintStream previous = System.out; + +		try { +			System.setOut(new PrintStream(new NullOutputStream())); +		} 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. +		FileUtils.copyFile(minecraftClientPatchedOfficialJar, minecraftMergedPatchedJar); + +		logger.lifecycle(":copying resources"); + +		// Copy resources +		MinecraftProvider minecraftProvider = getExtension().getMinecraftProvider(); +		copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedJar); +		copyNonClassFiles(minecraftProvider.minecraftServerJar, minecraftMergedPatchedJar); +	} + +	private void walkFileSystems(File source, File target, Predicate<Path> filter, Function<FileSystem, Iterable<Path>> toWalk, FsPathConsumer action) +			throws IOException { +		try (FileSystemUtil.FileSystemDelegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); +				FileSystemUtil.FileSystemDelegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { +			for (Path sourceDir : toWalk.apply(sourceFs.get())) { +				Path dir = sourceDir.toAbsolutePath(); +				Files.walk(dir) +						.filter(Files::isRegularFile) +						.filter(filter) +						.forEach(it -> { +							boolean root = dir.getParent() == null; + +							try { +								Path relativeSource = root ? it : dir.relativize(it); +								Path targetPath = targetFs.get().getPath(relativeSource.toString()); +								action.accept(sourceFs.get(), targetFs.get(), it, targetPath); +							} catch (IOException e) { +								throw new UncheckedIOException(e); +							} +						}); +			} +		} +	} + +	private void walkFileSystems(File source, File target, Predicate<Path> filter, FsPathConsumer action) throws IOException { +		walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); +	} + +	private void copyAll(File source, File target) throws IOException { +		walkFileSystems(source, target, it -> true, this::copyReplacing); +	} + +	private void copyMissingClasses(File source, File target) throws IOException { +		walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { +			if (Files.exists(targetPath)) return; +			Path parent = targetPath.getParent(); + +			if (parent != null) { +				Files.createDirectories(parent); +			} + +			Files.copy(sourcePath, targetPath); +		}); +	} + +	private void copyNonClassFiles(File source, File target) throws IOException { +		walkFileSystems(source, target, it -> !it.toString().endsWith(".class"), 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 { +		walkFileSystems(source, target, file -> true, 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 File getMergedJar() { +		return minecraftMergedPatchedJar; +	} + +	public boolean usesProjectCache() { +		return projectAt != null; +	} + +	public boolean isAtDirty() { +		return atDirty; +	} + +	@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..24f1c866 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -0,0 +1,76 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableMap; +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; + +public class PatchProvider extends DependencyProvider { +	public Path clientPatches; +	public Path serverPatches; +	public String forgeVersion; + +	public PatchProvider(Project project) { +		super(project); +	} + +	@Override +	public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { +		init(dependency.getDependency().getVersion()); + +		if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { +			getProject().getLogger().info(":extracting forge patches"); + +			Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge installer")).toPath(); + +			try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { +				Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); +				Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); +			} +		} +	} + +	private void init(String forgeVersion) { +		this.forgeVersion = forgeVersion; +		clientPatches = getExtension().getProjectPersistentCache().toPath().resolve("patches-" + forgeVersion + "-client.lzma"); +		serverPatches = getExtension().getProjectPersistentCache().toPath().resolve("patches-" + forgeVersion + "-server.lzma"); +	} + +	@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..1fe73c21 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -0,0 +1,78 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge; + +import java.io.File; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; + +import com.google.common.collect.ImmutableMap; +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.util.Constants; + +public class SrgProvider extends DependencyProvider { +	private File srg; + +	public SrgProvider(Project project) { +		super(project); +	} + +	@Override +	public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { +		init(dependency.getDependency().getVersion()); + +		if (srg.exists() && !isRefreshDeps()) { +			return; // No work for us to do here +		} + +		Path srgZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve srg")).toPath(); + +		if (!srg.exists() || isRefreshDeps()) { +			try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) { +				Files.copy(fs.getPath("config", "joined.tsrg"), srg.toPath(), StandardCopyOption.REPLACE_EXISTING); +			} +		} +	} + +	private void init(String version) { +		srg = new File(getExtension().getUserCache(), "srg-" + version + ".tsrg"); +	} + +	public File getSrg() { +		return srg; +	} + +	@Override +	public String getTargetConfig() { +		return Constants.Configurations.SRG; +	} +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java index 810a34f3..cf9fab37 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProvider.java @@ -31,9 +31,12 @@ import java.net.URL;  import java.nio.file.FileSystem;  import java.nio.file.FileSystems;  import java.nio.file.Files; +import java.nio.file.NoSuchFileException;  import java.nio.file.Path;  import java.nio.file.Paths;  import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List;  import java.util.function.Consumer;  import com.google.common.base.Preconditions; @@ -41,6 +44,7 @@ import com.google.common.net.UrlEscapers;  import org.apache.commons.io.FileUtils;  import org.apache.tools.ant.util.StringUtils;  import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration;  import org.zeroturnaround.zip.FileSource;  import org.zeroturnaround.zip.ZipEntrySource;  import org.zeroturnaround.zip.ZipUtil; @@ -51,19 +55,27 @@ import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;  import net.fabricmc.loom.configuration.processors.JarProcessorManager;  import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider;  import net.fabricmc.loom.configuration.providers.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.SrgProvider;  import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider;  import net.fabricmc.loom.util.Constants;  import net.fabricmc.loom.util.DeletingFileVisitor;  import net.fabricmc.loom.util.DownloadUtil; +import net.fabricmc.loom.util.srg.MCPReader; +import net.fabricmc.loom.util.srg.SrgMerger; +import net.fabricmc.loom.util.srg.SrgNamedWriter;  import net.fabricmc.mapping.reader.v2.TinyV2Factory;  import net.fabricmc.mapping.tree.TinyTree;  import net.fabricmc.stitch.Command;  import net.fabricmc.stitch.commands.CommandProposeFieldNames;  import net.fabricmc.stitch.commands.tinyv2.CommandMergeTinyV2;  import net.fabricmc.stitch.commands.tinyv2.CommandReorderTinyV2; +import net.fabricmc.stitch.commands.tinyv2.TinyFile; +import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer;  public class MappingsProvider extends DependencyProvider {  	public MinecraftMappedProvider mappedProvider; +	public MinecraftPatchedProvider patchedProvider;  	public String mappingsName;  	public String minecraftVersion; @@ -78,6 +90,10 @@ public class MappingsProvider extends DependencyProvider {  	// The mappings we use in practice  	public File tinyMappings;  	public File tinyMappingsJar; +	public File mappingsMixinExport; +	public Path tinyMappingsWithSrg; +	public File mixinTinyMappingsWithSrg; // FORGE: The mixin mappings have srg names in intermediary. +	public File srgToNamedSrg; // FORGE: srg to named in srg file format  	public MappingsProvider(Project project) {  		super(project); @@ -93,6 +109,14 @@ public class MappingsProvider extends DependencyProvider {  		return MappingsCache.INSTANCE.get(tinyMappings.toPath());  	} +	public TinyTree getMappingsWithSrg() throws IOException { +		if (getExtension().shouldGenerateSrgTiny()) { +			return MappingsCache.INSTANCE.get(tinyMappingsWithSrg); +		} + +		throw new UnsupportedOperationException("Not running with Forge support / Tiny srg support."); +	} +  	@Override  	public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception {  		MinecraftProvider minecraftProvider = getDependencyManager().getProvider(MinecraftProvider.class); @@ -107,6 +131,13 @@ public class MappingsProvider extends DependencyProvider {  		boolean isV2; +		if (isMCP(mappingsJar.toPath())) { +			File old = mappingsJar; +			mappingsJar = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".zip") + "-" + minecraftVersion + ".jar").toFile(); +			FileUtils.copyFile(old, mappingsJar); +			mappingsName += "-" + minecraftVersion; +		} +  		// Only do this for official yarn, there isn't really a way we can get the mc version for all mappings  		if (dependency.getDependency().getGroup() != null && dependency.getDependency().getGroup().equals("net.fabricmc") && dependency.getDependency().getName().equals("yarn") && dependency.getDependency().getVersion() != null) {  			String yarnVersion = dependency.getDependency().getVersion(); @@ -143,15 +174,41 @@ public class MappingsProvider extends DependencyProvider {  		tinyMappings = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + ".tiny").toFile();  		tinyMappingsJar = new File(getExtension().getUserCache(), mappingsJar.getName().replace(".jar", "-" + jarClassifier + ".jar")); +		tinyMappingsWithSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-srg.tiny"); +		mixinTinyMappingsWithSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-mixin-srg.tiny").toFile(); +		srgToNamedSrg = mappingsDir.resolve(StringUtils.removeSuffix(mappingsJar.getName(), ".jar") + "-srg-named.srg").toFile();  		if (!tinyMappings.exists() || isRefreshDeps()) { -			storeMappings(getProject(), minecraftProvider, mappingsJar.toPath()); +			storeMappings(getProject(), minecraftProvider, mappingsJar.toPath(), postPopulationScheduler);  		}  		if (!tinyMappingsJar.exists() || isRefreshDeps()) {  			ZipUtil.pack(new ZipEntrySource[] {new FileSource("mappings/mappings.tiny", tinyMappings)}, tinyMappingsJar);  		} +		if (getExtension().shouldGenerateSrgTiny()) { +			if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) { +				SrgMerger.mergeSrg(getExtension().getSrgProvider().getSrg().toPath(), tinyMappings.toPath(), tinyMappingsWithSrg, true); +			} +		} + +		if (getExtension().isForge()) { +			if (!getExtension().shouldGenerateSrgTiny()) { +				throw new IllegalStateException("We have to generate srg tiny in a forge environment!"); +			} + +			if (!mixinTinyMappingsWithSrg.exists() || isRefreshDeps()) { +				List<String> lines = new ArrayList<>(Files.readAllLines(tinyMappingsWithSrg)); +				lines.set(0, lines.get(0).replace("intermediary", "yraidemretni").replace("srg", "intermediary")); +				Files.deleteIfExists(mixinTinyMappingsWithSrg.toPath()); +				Files.write(mixinTinyMappingsWithSrg.toPath(), lines); +			} + +			if (!srgToNamedSrg.exists() || isRefreshDeps()) { +				SrgNamedWriter.writeTo(getProject().getLogger(), srgToNamedSrg.toPath(), getMappingsWithSrg(), "srg", "named"); +			} +		} +  		addDependency(tinyMappingsJar, Constants.Configurations.MAPPINGS_FINAL);  		LoomGradleExtension extension = getExtension(); @@ -164,7 +221,12 @@ public class MappingsProvider extends DependencyProvider {  		extension.setJarProcessorManager(processorManager);  		processorManager.setupProcessors(); -		if (processorManager.active()) { +		if (extension.isForge()) { +			patchedProvider = new MinecraftPatchedProvider(this, getProject()); +			patchedProvider.provide(dependency, postPopulationScheduler); +		} + +		if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) {  			mappedProvider = new MinecraftProcessedProvider(getProject(), processorManager);  			getProject().getLogger().lifecycle("Using project based jar storage");  		} else { @@ -175,9 +237,15 @@ public class MappingsProvider extends DependencyProvider {  		mappedProvider.provide(dependency, postPopulationScheduler);  	} -	private void storeMappings(Project project, MinecraftProvider minecraftProvider, Path yarnJar) throws IOException { +	private void storeMappings(Project project, MinecraftProvider minecraftProvider, Path yarnJar, Consumer<Runnable> postPopulationScheduler) +			throws Exception {  		project.getLogger().lifecycle(":extracting " + yarnJar.getFileName()); +		if (isMCP(yarnJar)) { +			readAndMergeMCP(yarnJar, postPopulationScheduler); +			return; +		} +  		try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) {  			extractMappings(fileSystem, baseTinyMappings);  		} @@ -196,6 +264,34 @@ public class MappingsProvider extends DependencyProvider {  		}  	} +	private void readAndMergeMCP(Path mcpJar, Consumer<Runnable> postPopulationScheduler) throws Exception { +		Path intermediaryTinyPath = getIntermediaryTiny(); +		SrgProvider provider = getExtension().getSrgProvider(); + +		if (provider == null) { +			if (!getExtension().shouldGenerateSrgTiny()) { +				Configuration srg = getProject().getConfigurations().maybeCreate(Constants.Configurations.SRG); +				srg.setTransitive(false); +			} + +			provider = new SrgProvider(getProject()); +			getProject().getDependencies().add(provider.getTargetConfig(), "de.oceanlabs.mcp:mcp_config:" + minecraftVersion); +			Configuration configuration = getProject().getConfigurations().getByName(provider.getTargetConfig()); +			provider.provide(DependencyInfo.create(getProject(), configuration.getDependencies().iterator().next(), configuration), postPopulationScheduler); +		} + +		Path srgPath = provider.getSrg().toPath(); + +		TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar); +		TinyV2Writer.write(file, tinyMappings.toPath()); +	} + +	private boolean isMCP(Path path) throws IOException { +		try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) { +			return Files.exists(fs.getPath("fields.csv")) && Files.exists(fs.getPath("methods.csv")); +		} +	} +  	private boolean baseMappingsAreV2() throws IOException {  		try (BufferedReader reader = Files.newBufferedReader(baseTinyMappings)) {  			TinyV2Factory.readMetadata(reader); @@ -211,7 +307,7 @@ public class MappingsProvider extends DependencyProvider {  			try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) {  				TinyV2Factory.readMetadata(reader);  				return true; -			} catch (IllegalArgumentException e) { +			} catch (IllegalArgumentException | NoSuchFileException e) {  				return false;  			}  		} @@ -258,20 +354,20 @@ public class MappingsProvider extends DependencyProvider {  		try {  			Command command = new CommandMergeTinyV2();  			runCommand(command, intermediaryMappings.toAbsolutePath().toString(), -							yarnMappings.toAbsolutePath().toString(), -							newMergedMappings.toAbsolutePath().toString(), -							"intermediary", "official"); +					yarnMappings.toAbsolutePath().toString(), +					newMergedMappings.toAbsolutePath().toString(), +					"intermediary", "official");  		} catch (Exception e) {  			throw new RuntimeException("Could not merge mappings from " + intermediaryMappings.toString() -							+ " with mappings from " + yarnMappings, e); +					+ " with mappings from " + yarnMappings, e);  		}  	}  	private void suggestFieldNames(MinecraftProvider 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) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java index 22664957..7ec517c0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MojangMappingsDependency.java @@ -45,9 +45,19 @@ 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.gradle.api.Action;  import org.gradle.api.Project;  import org.gradle.api.artifacts.Dependency; +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.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.tasks.TaskDependency;  import org.zeroturnaround.zip.ByteSource;  import org.zeroturnaround.zip.ZipEntrySource; @@ -60,7 +70,7 @@ import net.fabricmc.loom.util.DownloadUtil;  import net.fabricmc.lorenztiny.TinyMappingsReader;  import net.fabricmc.mapping.tree.TinyMappingFactory; -public class MojangMappingsDependency implements SelfResolvingDependency { +public class MojangMappingsDependency extends AbstractModuleDependency implements SelfResolvingDependency, ExternalModuleDependency {  	public static final String GROUP = "net.minecraft";  	public static final String MODULE = "mappings";  	// Keys in dependency manifest @@ -70,12 +80,51 @@ public class MojangMappingsDependency implements SelfResolvingDependency {  	private final Project project;  	private final LoomGradleExtension extension; +	private boolean changing; +	private boolean force; +  	public MojangMappingsDependency(Project project, LoomGradleExtension extension) { +		super(null);  		this.project = project;  		this.extension = extension;  	}  	@Override +	public ExternalModuleDependency copy() { +		MojangMappingsDependency copiedProjectDependency = new MojangMappingsDependency(project, extension); +		this.copyTo(copiedProjectDependency); +		return copiedProjectDependency; +	} + +	@Override +	public void version(Action<? super MutableVersionConstraint> action) { +	} + +	@Override +	public boolean isForce() { +		return this.force; +	} + +	@Override +	public ExternalModuleDependency setForce(boolean force) { +		this.validateMutation(this.force, force); +		this.force = force; +		return this; +	} + +	@Override +	public boolean isChanging() { +		return this.changing; +	} + +	@Override +	public ExternalModuleDependency setChanging(boolean changing) { +		this.validateMutation(this.changing, changing); +		this.changing = changing; +		return this; +	} + +	@Override  	public Set<File> resolve() {  		Path mappingsDir = extension.getMappingsProvider().getMappingsDir();  		Path mappingsFile = mappingsDir.resolve(String.format("%s.%s-%s.tiny", GROUP, MODULE, getVersion())); @@ -101,20 +150,22 @@ public class MojangMappingsDependency implements SelfResolvingDependency {  			}  		} -		try (BufferedReader clientBufferedReader = Files.newBufferedReader(clientMappings, StandardCharsets.UTF_8)) { -			project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); -			project.getLogger().warn("Using of the official minecraft mappings is at your own risk!"); -			project.getLogger().warn("Please make sure to read and understand the following license:"); -			project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); -			String line; +		if (!extension.isSilentMojangMappingsLicenseEnabled()) { +			try (BufferedReader clientBufferedReader = Files.newBufferedReader(clientMappings, StandardCharsets.UTF_8)) { +				project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); +				project.getLogger().warn("Using of the official minecraft mappings is at your own risk!"); +				project.getLogger().warn("Please make sure to read and understand the following license:"); +				project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); +				String line; -			while ((line = clientBufferedReader.readLine()).startsWith("#")) { -				project.getLogger().warn(line); -			} +				while ((line = clientBufferedReader.readLine()).startsWith("#")) { +					project.getLogger().warn(line); +				} -			project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); -		} catch (IOException e) { -			throw new RuntimeException("Failed to read client mappings", e); +				project.getLogger().warn("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); +			} catch (IOException e) { +				throw new RuntimeException("Failed to read client mappings", e); +			}  		}  		return Collections.singleton(mappingsFile.toFile()); @@ -160,7 +211,7 @@ public class MojangMappingsDependency implements SelfResolvingDependency {  						ClassMapping<?, ?> mojangClassMapping = intermediaryToMojang.getOrCreateClassMapping(inputMappings.getFullObfuscatedName())  								.setDeobfuscatedName(namedClass.getFullDeobfuscatedName()); -						for (FieldMapping fieldMapping : inputMappings .getFieldMappings()) { +						for (FieldMapping fieldMapping : inputMappings.getFieldMappings()) {  							namedClass.getFieldMapping(fieldMapping.getDeobfuscatedName())  									.ifPresent(namedField -> {  										mojangClassMapping.getOrCreateFieldMapping(fieldMapping.getSignature()) @@ -168,7 +219,7 @@ public class MojangMappingsDependency implements SelfResolvingDependency {  									});  						} -						for (MethodMapping methodMapping : inputMappings .getMethodMappings()) { +						for (MethodMapping methodMapping : inputMappings.getMethodMappings()) {  							namedClass.getMethodMapping(methodMapping.getDeobfuscatedSignature())  									.ifPresent(namedMethod -> {  										mojangClassMapping.getOrCreateMethodMapping(methodMapping.getSignature()) @@ -203,10 +254,26 @@ public class MojangMappingsDependency implements SelfResolvingDependency {  	@Override  	public String getVersion() { +		if (extension.getDependencyManager() == null) return "1.0.0";  		return extension.getMinecraftProvider().getMinecraftVersion();  	}  	@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 MojangMappingsDependency) {  			return ((MojangMappingsDependency) dependency).extension.getMinecraftProvider().getMinecraftVersion().equals(getVersion()); @@ -216,11 +283,6 @@ public class MojangMappingsDependency implements SelfResolvingDependency {  	}  	@Override -	public Dependency copy() { -		return new MojangMappingsDependency(project, extension); -	} - -	@Override  	public String getReason() {  		return null;  	} 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 6301050a..93068ba4 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 @@ -25,20 +25,36 @@  package net.fabricmc.loom.configuration.providers.minecraft;  import java.io.File; +import java.io.FileInputStream;  import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +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.util.Arrays;  import java.util.Map;  import java.util.function.Consumer; +import java.util.jar.Attributes; +import java.util.jar.Manifest;  import com.google.common.collect.ImmutableMap;  import org.gradle.api.Project; +import org.jetbrains.annotations.Nullable;  import net.fabricmc.loom.configuration.DependencyProvider;  import net.fabricmc.loom.configuration.providers.MinecraftProvider;  import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;  import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ThreadingUtils;  import net.fabricmc.loom.util.TinyRemapperMappingsHelper; +import net.fabricmc.loom.util.srg.AtRemapper; +import net.fabricmc.loom.util.srg.CoreModClassRemapper; +import net.fabricmc.loom.util.srg.InnerClassRemapper; +import net.fabricmc.mapping.tree.TinyTree; +import net.fabricmc.tinyremapper.NonClassCopyMode;  import net.fabricmc.tinyremapper.OutputConsumerPath;  import net.fabricmc.tinyremapper.TinyRemapper; @@ -49,8 +65,10 @@ public class MinecraftMappedProvider extends DependencyProvider {  			.put("javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable")  			.build(); +	private File inputJar;  	private File minecraftMappedJar;  	private File minecraftIntermediaryJar; +	private File minecraftSrgJar;  	private MinecraftProvider minecraftProvider; @@ -64,11 +82,13 @@ 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(); + +		if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || (getExtension().isForge() && !getSrgJar().exists()) || isRefreshDeps() || isForgeAtDirty) {  			if (minecraftMappedJar.exists()) {  				minecraftMappedJar.delete();  			} @@ -79,12 +99,21 @@ public class MinecraftMappedProvider extends DependencyProvider {  				minecraftIntermediaryJar.delete();  			} +			if (getExtension().isForge() && minecraftSrgJar.exists()) { +				minecraftSrgJar.delete(); +			} +  			try {  				mapMinecraftJar();  			} catch (Throwable t) {  				// Cleanup some some things that may be in a bad state now  				minecraftMappedJar.delete();  				minecraftIntermediaryJar.delete(); + +				if (getExtension().isForge()) { +					minecraftSrgJar.delete(); +				} +  				getExtension().getMappingsProvider().cleanFiles();  				throw new RuntimeException("Failed to remap minecraft", t);  			} @@ -97,46 +126,104 @@ public class MinecraftMappedProvider extends DependencyProvider {  		addDependencies(dependency, postPopulationScheduler);  	} -	private void mapMinecraftJar() throws IOException { +	private void mapMinecraftJar() throws Exception {  		String fromM = "official";  		MappingsProvider mappingsProvider = getExtension().getMappingsProvider(); -		Path input = minecraftProvider.getMergedJar().toPath(); +		Path input = inputJar.toPath();  		Path outputMapped = minecraftMappedJar.toPath();  		Path outputIntermediary = minecraftIntermediaryJar.toPath(); +		Path outputSrg = minecraftSrgJar == null ? null : minecraftSrgJar.toPath(); + +		Path[] libraries = getRemapClasspath(getProject()); -		for (String toM : Arrays.asList("named", "intermediary")) { -			Path output = "named".equals(toM) ? outputMapped : outputIntermediary; +		ThreadingUtils.run(getExtension().isForge() ? Arrays.asList("named", "intermediary", "srg") : Arrays.asList("named", "intermediary"), toM -> { +			Path output = "named".equals(toM) ? outputMapped : "srg".equals(toM) ? outputSrg : outputIntermediary;  			getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")"); -			TinyRemapper remapper = getTinyRemapper(fromM, toM); +			TinyRemapper remapper = getTinyRemapper(input, fromM, toM);  			try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) { -				outputConsumer.addNonClassFiles(input); -				remapper.readClassPath(getRemapClasspath()); +				if (getExtension().isForge()) { +					outputConsumer.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, remapper); +				} else { +					outputConsumer.addNonClassFiles(input); +				} + +				remapper.readClassPath(libraries);  				remapper.readInputs(input);  				remapper.apply(outputConsumer);  			} catch (Exception e) { +				Files.deleteIfExists(output);  				throw new RuntimeException("Failed to remap JAR " + input + " with mappings from " + mappingsProvider.tinyMappings, e);  			} finally {  				remapper.finish();  			} -		} + +			if (getExtension().isForge() && !"srg".equals(toM)) { +				getProject().getLogger().info(":running forge finalising tasks"); + +				// TODO: Relocate this to its own class +				try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + output.toUri()), ImmutableMap.of("create", false))) { +					Path manifestPath = fs.getPath("META-INF", "MANIFEST.MF"); +					Manifest minecraftManifest; +					Manifest forgeManifest; + +					try (InputStream in = Files.newInputStream(manifestPath)) { +						minecraftManifest = new Manifest(in); +					} + +					try (InputStream in = new FileInputStream(getExtension().getForgeUniversalProvider().getForgeManifest())) { +						forgeManifest = new Manifest(in); +					} + +					for (Map.Entry<String, Attributes> forgeEntry : forgeManifest.getEntries().entrySet()) { +						if (forgeEntry.getKey().endsWith("/")) { +							minecraftManifest.getEntries().put(forgeEntry.getKey(), forgeEntry.getValue()); +						} +					} + +					Files.delete(manifestPath); + +					try (OutputStream out = Files.newOutputStream(manifestPath)) { +						minecraftManifest.write(out); +					} +				} + +				TinyTree yarnWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); +				AtRemapper.remap(getProject().getLogger(), output, yarnWithSrg); +				CoreModClassRemapper.remapJar(output, yarnWithSrg, getProject().getLogger()); +			} +		});  	} -	public TinyRemapper getTinyRemapper(String fromM, String toM) throws IOException { -		return TinyRemapper.newRemapper() -				.withMappings(TinyRemapperMappingsHelper.create(getExtension().getMappingsProvider().getMappings(), fromM, toM, true)) -				.withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass)) +	public TinyRemapper getTinyRemapper(@Nullable Path fromJar, String fromM, String toM) throws IOException { +		TinyRemapper.Builder builder = TinyRemapper.newRemapper() +				.withMappings(TinyRemapperMappingsHelper.create(getExtension().isForge() ? getExtension().getMappingsProvider().getMappingsWithSrg() : getExtension().getMappingsProvider().getMappings(), fromM, toM, true))  				.renameInvalidLocals(true) -				.rebuildSourceFilenames(true) -				.build(); +				.ignoreConflicts(getExtension().isForge()) +				.rebuildSourceFilenames(true); + +		if (getExtension().isForge()) { +			/* FORGE: Required for classes like aej$OptionalNamedTag (1.16.4) which are added by Forge patches. +			 * They won't get remapped to their proper packages, so IllegalAccessErrors will happen without ._. +			 */ +			builder.fixPackageAccess(true); + +			if (fromJar != null) { +				builder.withMappings(InnerClassRemapper.of(fromJar, getExtension().getMappingsProvider().getMappingsWithSrg(), fromM, toM)); +			} +		} else { +			builder.withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass)); +		} + +		return builder.build();  	} -	public Path[] getRemapClasspath() { -		return getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles() +	public static Path[] getRemapClasspath(Project project) { +		return project.getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).getFiles()  				.stream().map(File::toPath).toArray(Path[]::new);  	} @@ -150,7 +237,9 @@ public class MinecraftMappedProvider extends DependencyProvider {  	public void initFiles(MinecraftProvider minecraftProvider, MappingsProvider mappingsProvider) {  		this.minecraftProvider = minecraftProvider;  		minecraftIntermediaryJar = new File(getExtension().getUserCache(), "minecraft-" + getJarVersionString("intermediary") + ".jar"); +		minecraftSrgJar = !getExtension().isForge() ? null : new File(getExtension().getUserCache(), "minecraft-" + getJarVersionString("srg") + ".jar");  		minecraftMappedJar = new File(getJarDirectory(getExtension().getUserCache(), "mapped"), "minecraft-" + getJarVersionString("mapped") + ".jar"); +		inputJar = getExtension().isForge() ? mappingsProvider.patchedProvider.getMergedJar() : minecraftProvider.getMergedJar();  	}  	protected File getJarDirectory(File parentDirectory, String type) { @@ -158,13 +247,17 @@ public class MinecraftMappedProvider extends DependencyProvider {  	}  	protected String getJarVersionString(String type) { -		return String.format("%s-%s-%s-%s", minecraftProvider.getMinecraftVersion(), type, getExtension().getMappingsProvider().mappingsName, getExtension().getMappingsProvider().mappingsVersion); +		return String.format("%s-%s-%s-%s%s", minecraftProvider.getMinecraftVersion(), type, getExtension().getMappingsProvider().mappingsName, getExtension().getMappingsProvider().mappingsVersion, minecraftProvider.getJarSuffix());  	}  	public File getIntermediaryJar() {  		return minecraftIntermediaryJar;  	} +	public File getSrgJar() { +		return minecraftSrgJar; +	} +  	public File getMappedJar() {  		return minecraftMappedJar;  	} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftNativesProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftNativesProvider.java index 9f338ef8..7a8b05cc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftNativesProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftNativesProvider.java @@ -28,6 +28,7 @@ import java.io.File;  import java.io.IOException;  import java.net.URL; +import com.google.common.base.Stopwatch;  import org.gradle.api.GradleException;  import org.gradle.api.Project;  import org.zeroturnaround.zip.ZipUtil; @@ -35,6 +36,7 @@ import org.zeroturnaround.zip.ZipUtil;  import net.fabricmc.loom.LoomGradleExtension;  import net.fabricmc.loom.configuration.providers.MinecraftProvider;  import net.fabricmc.loom.util.DownloadUtil; +import net.fabricmc.loom.util.ThreadingUtils;  public class MinecraftNativesProvider {  	public static void provide(MinecraftProvider minecraftProvider, Project project) throws IOException { @@ -53,7 +55,9 @@ public class MinecraftNativesProvider {  			return;  		} -		for (MinecraftVersionInfo.Library library : versionInfo.libraries) { +		Stopwatch stopwatch = Stopwatch.createStarted(); + +		ThreadingUtils.run(versionInfo.libraries, library -> {  			File libJarFile = library.getFile(jarStore);  			if (library.allowed() && library.isNative() && libJarFile != null) { @@ -68,6 +72,8 @@ public class MinecraftNativesProvider {  				// TODO possibly find a way to prevent needing to re-extract after each run, doesnt seem too slow  				ZipUtil.unpack(libJarFile, nativesDir);  			} -		} +		}); + +		project.getLogger().info("Took " + stopwatch.stop() + " to provide " + versionInfo.libraries.size() + " natives.");  	}  } diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java index 61dbaf07..a8844f22 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java +++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/AbstractFernFlowerDecompiler.java @@ -45,6 +45,7 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;  import net.fabricmc.loom.api.decompilers.DecompilationMetadata;  import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.util.Constants;  import net.fabricmc.loom.util.ConsumingOutputStream;  import net.fabricmc.loom.util.OperatingSystem; @@ -100,7 +101,7 @@ public abstract class AbstractFernFlowerDecompiler implements LoomDecompiler {  		progressGroup.started();  		ExecResult result = ForkingJavaExec.javaexec( -				project.getRootProject().getPlugins().hasPlugin("fabric-loom") ? project.getRootProject() : project, +				project.getRootProject().getPlugins().hasPlugin(Constants.PLUGIN_ID) ? project.getRootProject() : project,  				spec -> {  					spec.setMain(fernFlowerExecutor().getName());  					spec.jvmArgs("-Xms200m", "-Xmx3G"); diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java index 8b5f5f60..e4b61b02 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java +++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/ForkingJavaExec.java @@ -42,7 +42,8 @@ public class ForkingJavaExec {  	public static ExecResult javaexec(Project project, Action<? super JavaExecSpec> action) {  		ConfigurationContainer configurations = project.getBuildscript().getConfigurations();  		DependencyHandler handler = project.getDependencies(); -		FileCollection classpath = configurations.getByName("classpath")// +		FileCollection classpath = project.getBuildscript().getConfigurations().getByName("classpath") +						.plus(project.getRootProject().getBuildscript().getConfigurations().getByName("classpath"))  						.plus(configurations.detachedConfiguration(handler.localGroovy()));  		return project.javaexec(spec -> { diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index d1655d41..5f47cc17 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -86,6 +86,7 @@ public abstract class AbstractRunTask extends JavaExec {  		args(argsSplit);  		setWorkingDir(new File(getProject().getRootDir(), config.runDir)); +		environment(config.envVariables);  		super.exec();  	} diff --git a/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java b/src/main/java/net/fabricmc/loom/task/GenEclipseRunsTask.java index 5cb45995..1f93ef0b 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.getRuns()) {  			String name = settings.getName(); diff --git a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java index fe142df5..e6ea643f 100644 --- a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java @@ -28,11 +28,16 @@ import java.io.File;  import java.io.IOException;  import java.nio.charset.StandardCharsets;  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; @@ -47,9 +52,12 @@ import net.fabricmc.loom.configuration.ide.RunConfigSettings;  public class GenVsCodeProjectTask extends AbstractLoomTask {  	@TaskAction  	public void genRuns() { -		Project project = getProject(); -		LoomGradleExtension extension = getExtension(); -		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(); @@ -60,15 +68,38 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {  		if (launchJson.exists()) {  			launchJson.delete();  		} +	} + +	public static void generate(Project project) { +		LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); +		File projectDir = project.getRootProject().file(".vscode"); + +		if (!projectDir.exists()) { +			projectDir.mkdir(); +		} -		VsCodeLaunch launch = new VsCodeLaunch(); +		File launchJson = new File(projectDir, "launch.json"); +		File tasksJson = new File(projectDir, "tasks.json"); + +		Gson gson = new GsonBuilder().setPrettyPrinting().create(); + +		VsCodeLaunch launch; -		for (RunConfigSettings settings : getExtension().getRuns()) { +		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.getRuns()) {  			launch.add(RunConfig.runConfig(project, settings));  			settings.makeRunDir();  		} -		Gson gson = new GsonBuilder().setPrettyPrinting().create();  		String json = gson.toJson(launch);  		try { @@ -76,6 +107,39 @@ 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 static class VsCodeLaunch { @@ -83,7 +147,25 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {  		public List<VsCodeConfiguration> configurations = new ArrayList<>();  		public void add(RunConfig runConfig) { -			configurations.add(new VsCodeConfiguration(runConfig)); +			if (configurations.stream().noneMatch(config -> Objects.equals(config.name, runConfig.configName))) { +				VsCodeConfiguration configuration = new VsCodeConfiguration(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); +			}  		}  	} @@ -98,6 +180,10 @@ 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) {  			this.name = runConfig.configName; @@ -105,6 +191,24 @@ public class GenVsCodeProjectTask extends AbstractLoomTask {  			this.vmArgs = runConfig.vmArgs;  			this.args = runConfig.programArgs;  			this.cwd = "${workspaceFolder}/" + runConfig.runDir; +			this.projectName = runConfig.vscodeProjectName; +			this.env.putAll(runConfig.envVariables); +			this.tasksBeforeRun.addAll(runConfig.vscodeBeforeRun); +		} +	} + +	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/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 2d8dfc69..0fdd28cd 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -89,6 +89,12 @@ public final class LoomTasks {  		extension.getRuns().create("server", RunConfigSettings::server);  		project.afterEvaluate(p -> { +			if (extension.isDataGenEnabled()) { +				extension.getRuns().create("data", RunConfigSettings::data); +			} +		}); + +		project.afterEvaluate(p -> {  			for (RunConfigSettings config : extension.getRuns()) {  				String configName = config.getName();  				String taskName = "run" + configName.substring(0, 1).toUpperCase() + configName.substring(1); diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index ab6306c8..78446707 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -27,12 +27,21 @@ package net.fabricmc.loom.task;  import java.io.File;  import java.io.FileNotFoundException;  import java.io.IOException; +import java.io.Reader; +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.util.ArrayList;  import java.util.List;  import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser;  import org.gradle.api.Action;  import org.gradle.api.Project;  import org.gradle.api.file.FileCollection; @@ -50,6 +59,7 @@ import net.fabricmc.loom.build.MixinRefmapHelper;  import net.fabricmc.loom.build.NestedJars;  import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor;  import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; +import net.fabricmc.loom.util.LoggerFilter;  import net.fabricmc.loom.util.TinyRemapperMappingsHelper;  import net.fabricmc.loom.util.ZipReprocessorUtil;  import net.fabricmc.loom.util.gradle.GradleSupport; @@ -63,6 +73,8 @@ public class RemapJarTask extends Jar {  	private final Property<Boolean> addNestedDependencies;  	private final Property<Boolean> remapAccessWidener;  	private final List<Action<TinyRemapper.Builder>> remapOptions = new ArrayList<>(); +	private final Property<String> fromM; +	private final Property<String> toM;  	public JarRemapper jarRemapper;  	private FileCollection classpath; @@ -71,6 +83,10 @@ public class RemapJarTask extends Jar {  		input = GradleSupport.getfileProperty(getProject());  		addNestedDependencies = getProject().getObjects().property(Boolean.class);  		remapAccessWidener = getProject().getObjects().property(Boolean.class); +		fromM = getProject().getObjects().property(String.class); +		toM = getProject().getObjects().property(String.class); +		fromM.set("named"); +		toM.set("intermediary");  		// false by default, I have no idea why I have to do it for this property and not the other one  		remapAccessWidener.set(false);  	} @@ -96,18 +112,19 @@ public class RemapJarTask extends Jar {  		MappingsProvider mappingsProvider = extension.getMappingsProvider(); -		String fromM = "named"; -		String toM = "intermediary"; +		String fromM = this.fromM.get(); +		String toM = this.toM.get();  		Path[] classpath = getRemapClasspath(); +		LoggerFilter.replaceSystemOut();  		TinyRemapper.Builder remapperBuilder = TinyRemapper.newRemapper(); -		remapperBuilder = remapperBuilder.withMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false)); +		remapperBuilder = remapperBuilder.withMappings(TinyRemapperMappingsHelper.create(extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(), fromM, toM, false));  		for (File mixinMapFile : extension.getAllMixinMappings()) {  			if (mixinMapFile.exists()) { -				remapperBuilder = remapperBuilder.withMappings(TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, toM)); +				remapperBuilder = remapperBuilder.withMappings(TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, "intermediary"));  			}  		} @@ -152,6 +169,30 @@ public class RemapJarTask extends Jar {  			project.getLogger().debug("Transformed mixin reference maps in output JAR!");  		} +		if (extension.isForge()) { +			try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + output.toUri()), ImmutableMap.of("create", false))) { +				Path refmapPath = fs.getPath(extension.getRefmapName()); + +				if (Files.exists(refmapPath)) { +					try (Reader refmapReader = Files.newBufferedReader(refmapPath, StandardCharsets.UTF_8)) { +						JsonObject refmapElement = new JsonParser().parse(refmapReader).getAsJsonObject().deepCopy(); +						Files.delete(refmapPath); + +						if (refmapElement.has("data")) { +							JsonObject data = refmapElement.get("data").getAsJsonObject(); + +							if (data.has("named:intermediary")) { +								data.add("searge", data.get("named:intermediary").deepCopy()); +								data.remove("named:intermediary"); +							} +						} + +						Files.write(refmapPath, new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create().toJson(refmapElement).getBytes(StandardCharsets.UTF_8)); +					} +				} +			} +		} +  		if (getAddNestedDependencies().getOrElse(false)) {  			if (NestedJars.addNestedJars(project, output)) {  				project.getLogger().debug("Added nested jar paths to mod json"); @@ -175,18 +216,18 @@ public class RemapJarTask extends Jar {  		MappingsProvider mappingsProvider = extension.getMappingsProvider(); -		String fromM = "named"; -		String toM = "intermediary"; +		String fromM = this.fromM.get(); +		String toM = this.toM.get();  		if (extension.isRootProject()) {  			jarRemapper.addToClasspath(getRemapClasspath()); -			jarRemapper.addMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false)); +			jarRemapper.addMappings(TinyRemapperMappingsHelper.create(extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(), fromM, toM, false));  		}  		for (File mixinMapFile : extension.getAllMixinMappings()) {  			if (mixinMapFile.exists()) { -				jarRemapper.addMappings(TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, toM)); +				jarRemapper.addMappings(TinyUtils.createTinyMappingProvider(mixinMapFile.toPath(), fromM, "intermediary"));  			}  		} @@ -276,4 +317,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/RunDataTask.java b/src/main/java/net/fabricmc/loom/task/RunDataTask.java new file mode 100644 index 00000000..fbb80951 --- /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) 2016, 2017, 2018 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 = project.getExtensions().getByType(LoomGradleExtension.class); +			return RunConfig.runConfig(project, extension.getRuns().getByName("data")); +		}); +	} +} diff --git a/src/main/java/net/fabricmc/loom/util/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java index 1b38a051..23690905 100644 --- a/src/main/java/net/fabricmc/loom/util/Checksum.java +++ b/src/main/java/net/fabricmc/loom/util/Checksum.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.util;  import java.io.File;  import java.io.IOException; +import java.nio.charset.StandardCharsets;  import com.google.common.hash.HashCode;  import com.google.common.hash.Hashing; @@ -43,8 +44,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();  		} @@ -60,4 +62,9 @@ public class Checksum {  			throw new RuntimeException("Failed to get file hash");  		}  	} + +	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 f130a824..0ff48728 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.util.gradle.GradleSupport;  public class Constants { +	public static final String PLUGIN_ID = "forgified-fabric-loom";  	public static final String LIBRARIES_BASE = "https://libraries.minecraft.net/";  	public static final String RESOURCES_BASE = "http://resources.download.minecraft.net/";  	public static final String VERSION_MANIFESTS = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; @@ -76,6 +77,13 @@ public class Constants {  		public static final String MAPPINGS = "mappings";  		public static final String MAPPINGS_FINAL = "mappingsFinal";  		public static final String LOADER_DEPENDENCIES = "loaderLibraries"; +		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"; +		public static final String FORGE_DEPENDENCIES = "forgeDependencies";  		@Deprecated // Not to be used in gradle 7+  		public static final String COMPILE = "compile"; @@ -91,6 +99,7 @@ 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.  		private Dependencies() {  		} @@ -103,6 +112,7 @@ 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 = "19.0.0"; +			public static final String JAVAX_ANNOTATIONS = "3.0.2";  			private Versions() {  			} @@ -134,4 +144,11 @@ public class Constants {  		private Knot() {  		}  	} +	 +	public static final class ForgeUserDev { +		public static final String LAUNCH_TESTING = "net.minecraftforge.userdev.LaunchTesting"; + +		private ForgeUserDev() { +		} +	}  } diff --git a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java index 9bb2b7e2..8a16ea91 100644 --- a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java +++ b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java @@ -46,8 +46,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);  	}  	/** @@ -59,7 +59,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) { @@ -99,7 +99,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(); @@ -131,6 +131,8 @@ public class DownloadUtil {  			saveETag(to, eTag, logger);  		} + +		return true;  	}  	/** diff --git a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java new file mode 100644 index 00000000..cb37d2f3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *     http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.loom.util; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public final class FileSystemUtil { +	public static class FileSystemDelegate implements AutoCloseable { +		private final FileSystem fileSystem; +		private final boolean owner; + +		public FileSystemDelegate(FileSystem fileSystem, boolean owner) { +			this.fileSystem = fileSystem; +			this.owner = owner; +		} + +		public FileSystem get() { +			return fileSystem; +		} + +		@Override +		public void close() throws IOException { +			if (owner) { +				fileSystem.close(); +			} +		} +	} + +	private FileSystemUtil() { +	} + +	private static final Map<String, String> jfsArgsCreate = new HashMap<>(); +	private static final Map<String, String> jfsArgsEmpty = new HashMap<>(); + +	static { +		jfsArgsCreate.put("create", "true"); +	} + +	public static FileSystemDelegate getJarFileSystem(File file, boolean create) throws IOException { +		return getJarFileSystem(file.toURI(), create); +	} + +	public static FileSystemDelegate getJarFileSystem(Path path, boolean create) throws IOException { +		return getJarFileSystem(path.toUri(), create); +	} + +	public static FileSystemDelegate getJarFileSystem(URI uri, boolean create) throws IOException { +		URI jarUri; + +		try { +			jarUri = new URI("jar:" + uri.getScheme(), uri.getHost(), uri.getPath(), uri.getFragment()); +		} catch (URISyntaxException e) { +			throw new IOException(e); +		} + +		try { +			return new FileSystemDelegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true); +		} catch (FileSystemAlreadyExistsException e) { +			return new FileSystemDelegate(FileSystems.getFileSystem(jarUri), false); +		} +	} +} diff --git a/src/main/java/net/fabricmc/loom/util/JarUtil.java b/src/main/java/net/fabricmc/loom/util/JarUtil.java new file mode 100644 index 00000000..29eb996c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/JarUtil.java @@ -0,0 +1,50 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.io.IOException; +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 com.google.common.collect.ImmutableMap; + +/** + * Working with jars. + * + * @author Juuz + */ +public final class JarUtil { +	public static void extractFile(File jar, String filePath, File target) throws IOException { +		try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toURI()), ImmutableMap.of("create", false))) { +			Path targetPath = target.toPath(); +			Files.deleteIfExists(targetPath); +			Files.copy(fs.getPath(filePath), targetPath); +		} +	} +} 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..b7803c34 --- /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) 2016, 2017, 2018 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/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index b8a0a3f8..750fbdeb 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -194,6 +194,10 @@ 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()); +			} +  			Dependency annotationDependency = extension.getDependencyManager().getProvider(LaunchProvider.class).annotationDependency;  			Set<File> files = project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)  					.files(annotationDependency); 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..0e58cfa5 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java @@ -0,0 +1,124 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.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; + +public class ThreadingUtils { +	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.min(jobs.size(), Runtime.getRuntime().availableProcessors() / 2)); +			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.min(jobs.size(), Runtime.getRuntime().availableProcessors() / 2)); +			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; +	} +} 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..37d648d8 --- /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) 2016, 2017, 2018 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..f17364b2 --- /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) 2016, 2017, 2018 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..0974d95f --- /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) 2016, 2017, 2018 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..ff53aace --- /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) 2016, 2017, 2018 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..467c15a4 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java @@ -0,0 +1,134 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.UnaryOperator; +import java.util.zip.ZipEntry; + +import org.apache.logging.log4j.util.Strings; +import org.gradle.api.logging.Logger; +import org.zeroturnaround.zip.ZipUtil; +import org.zeroturnaround.zip.transform.StringZipEntryTransformer; +import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; + +import net.fabricmc.loom.util.function.CollectionUtil; +import net.fabricmc.mapping.tree.TinyTree; + +/** + * Remaps AT classes from SRG to Yarn. + * + * @author Juuz + */ +public final class AtRemapper { +	public static void remap(Logger logger, Path jar, TinyTree mappings) throws IOException { +		ZipUtil.transformEntries(jar.toFile(), new ZipEntryTransformerEntry[] {(new ZipEntryTransformerEntry("META-INF/accesstransformer.cfg", new StringZipEntryTransformer() { +			@Override +			protected String transform(ZipEntry zipEntry, String input) { +				String[] lines = input.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("#") || Strings.isBlank(line)) { +						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)); +				} + +				return String.join("\n", output); +			} +		}))}); +	} + +	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..9fb93079 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java @@ -0,0 +1,116 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.apache.logging.log4j.util.Strings; +import org.gradle.api.logging.Logger; + +import net.fabricmc.loom.util.function.CollectionUtil; +import net.fabricmc.mapping.tree.TinyTree; + +/** + * 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, TinyTree 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, TinyTree 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(Strings.LINE_SEPARATOR, 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..893dfe1b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java @@ -0,0 +1,78 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.zeroturnaround.zip.ZipUtil; + +import net.fabricmc.mapping.tree.ClassDef; +import net.fabricmc.mapping.tree.TinyTree; +import net.fabricmc.tinyremapper.IMappingProvider; + +public class InnerClassRemapper { +	public static IMappingProvider of(Path fromJar, TinyTree mappingsWithSrg, String from, String to) throws IOException { +		return sink -> { +			remapInnerClass(fromJar, mappingsWithSrg, from, to, sink::acceptClass); +		}; +	} + +	private static void remapInnerClass(Path fromJar, TinyTree mappingsWithSrg, String from, String to, BiConsumer<String, String> action) { +		try (InputStream inputStream = Files.newInputStream(fromJar)) { +			Map<String, String> availableClasses = mappingsWithSrg.getClasses().stream() +					.collect(Collectors.groupingBy(classDef -> classDef.getName(from), +							Collectors.<ClassDef, String>reducing( +									null, +									classDef -> classDef.getName(to), +									(first, last) -> last +							)) +					); +			ZipUtil.iterate(inputStream, (in, zipEntry) -> { +				if (!zipEntry.isDirectory() && zipEntry.getName().contains("$") && zipEntry.getName().endsWith(".class")) { +					String className = zipEntry.getName().substring(0, zipEntry.getName().length() - 6); + +					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)) { +							action.accept(className, remappedName); +						} +					} +				} +			}); +		} catch (IOException e) { +			throw new RuntimeException(e); +		} +	} +} 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..d45fea9d --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java @@ -0,0 +1,346 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.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.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import au.com.bytecode.opencsv.CSVReader; +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.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<>(); +		injectMcp(mcpJar, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap); + +		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 (TSrgReader reader = new TSrgReader(Files.newBufferedReader(srgTsrgPath, StandardCharsets.UTF_8))) { +			MappingSet mappingSet = reader.read(); + +			for (TopLevelClassMapping classMapping : mappingSet.getTopLevelClassMappings()) { +				appendClass(tokens, classMapping); +			} +		} + +		return tokens; +	} + +	private void injectMcp(Path mcpJar, Map<String, String> intermediaryToSrgMap, Map<String, String[]> intermediaryToDocsMap, Map<String, Map<Integer, String>> intermediaryToParamsMap) +			throws IOException { +		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, 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); +							} +						} +					} +				} +			} + +			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 static class MemberToken { +		private final TokenType type; +		@Nullable +		private final MemberToken owner; +		private final String name; +		@Nullable +		private final String descriptor; + +		MemberToken(TokenType type, @Nullable MemberToken owner, String name, @Nullable String descriptor) { +			this.type = type; +			this.owner = owner; +			this.name = name; +			this.descriptor = 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); +		} + +		@Override +		public boolean equals(Object o) { +			if (this == o) return true; +			if (o == null || getClass() != o.getClass()) return false; +			MemberToken that = (MemberToken) o; +			return type == that.type && name.equals(that.name) && Objects.equals(descriptor, that.descriptor) && Objects.equals(owner, that.owner); +		} + +		@Override +		public int hashCode() { +			return Objects.hash(type, name, descriptor, owner); +		} +	} + +	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..ada083ce --- /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) 2016, 2017, 2018 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..72a482cf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -0,0 +1,108 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.util.Arrays; +import java.util.Set; +import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; + +import org.apache.commons.io.IOUtils; +import org.gradle.api.Project; +import org.zeroturnaround.zip.ZipUtil; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; + +public class SpecialSourceExecutor { +	public static Path produceSrgJar(Project project, MappingsProvider provider, String side, File specialSourceJar, Path officialJar, Path srgPath) +			throws Exception { +		Set<String> filter = Files.readAllLines(srgPath, StandardCharsets.UTF_8).stream() +				.filter(s -> !s.startsWith("\t")) +				.map(s -> s.split(" ")[0] + ".class") +				.collect(Collectors.toSet()); +		LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); +		Path stripped = extension.getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); +		Files.deleteIfExists(stripped); + +		try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(stripped))) { +			ZipUtil.iterate(officialJar.toFile(), (in, zipEntry) -> { +				if (filter.contains(zipEntry.getName())) { +					output.putNextEntry((ZipEntry) zipEntry.clone()); +					IOUtils.write(IOUtils.toByteArray(in), output); +					output.closeEntry(); +				} +			}); +		} + +		Path output = extension.getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar"); +		Files.deleteIfExists(output); + +		String[] args = new String[] { +				"--in-jar", +				stripped.toAbsolutePath().toString(), +				"--out-jar", +				output.toAbsolutePath().toString(), +				"--srg-in", +				srgPath.toAbsolutePath().toString() +		}; + +		project.getLogger().lifecycle(":remapping minecraft (SpecialSource, " + side + ", official -> srg)"); + +		Path workingDir = tmpDir(); + +		project.javaexec(spec -> { +			spec.setArgs(Arrays.asList(args)); +			spec.setClasspath(project.files(specialSourceJar)); +			spec.workingDir(workingDir.toFile()); +			spec.setMain("net.md_5.specialsource.SpecialSource"); +			spec.setStandardOutput(System.out); +			spec.setErrorOutput(System.out); +		}).rethrowFailure().assertNormalExitValue(); + +		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..9f9fbe7d --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java @@ -0,0 +1,181 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 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.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +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.loom.util.function.CollectionUtil; +import net.fabricmc.mapping.tree.ClassDef; +import net.fabricmc.mapping.tree.FieldDef; +import net.fabricmc.mapping.tree.MethodDef; +import net.fabricmc.mapping.tree.TinyMappingFactory; +import net.fabricmc.mapping.tree.TinyTree; +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.TinyHeader; +import net.fabricmc.stitch.commands.tinyv2.TinyMethod; +import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer; + +/** + * Utilities for merging SRG mappings. + * + * @author Juuz + */ +public final class SrgMerger { +	/** +	 * Merges SRG mappings with a tiny mappings tree through the obf names. +	 * +	 * @param srg     the SRG file in .tsrg format +	 * @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(Path srg, Path tiny, Path out, boolean lenient) throws IOException, MappingException { +		MappingSet arr; +		TinyTree foss; + +		try (TSrgReader reader = new TSrgReader(Files.newBufferedReader(srg))) { +			arr = reader.read(); +		} + +		try (BufferedReader reader = Files.newBufferedReader(tiny)) { +			foss = TinyMappingFactory.loadWithDetection(reader); +		} + +		List<String> namespaces = new ArrayList<>(foss.getMetadata().getNamespaces()); +		namespaces.add(1, "srg"); + +		if (!"official".equals(namespaces.get(0))) { +			throw new MappingException("Mapping file " + tiny + " does not have the 'official' namespace as the default!"); +		} + +		TinyHeader header = new TinyHeader(namespaces, 2, 0, Collections.emptyMap()); + +		List<TinyClass> classes = new ArrayList<>(); + +		for (TopLevelClassMapping klass : arr.getTopLevelClassMappings()) { +			classToTiny(foss, namespaces, klass, classes::add, lenient); +		} + +		TinyFile file = new TinyFile(header, classes); +		TinyV2Writer.write(file, out); +	} + +	private static void classToTiny(TinyTree foss, List<String> namespaces, ClassMapping<?, ?> klass, Consumer<TinyClass> classConsumer, boolean lenient) { +		String obf = klass.getFullObfuscatedName(); +		String srg = klass.getFullDeobfuscatedName(); +		ClassDef classDef = foss.getDefaultNamespaceClassMap().get(obf); + +		if (classDef == null) { +			if (lenient) { +				return; +			} else { +				throw new MappingException("Missing class: " + obf + " (srg: " + srg + ")"); +			} +		} + +		List<String> classNames = CollectionUtil.map( +				namespaces, +				namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace) +		); + +		List<TinyMethod> methods = new ArrayList<>(); +		List<TinyField> fields = new ArrayList<>(); + +		for (MethodMapping method : klass.getMethodMappings()) { +			MethodDef def = CollectionUtil.find( +					classDef.getMethods(), +					m -> m.getName("official").equals(method.getObfuscatedName()) && m.getDescriptor("official").equals(method.getObfuscatedDescriptor()) +			).orElse(nullOrThrow(lenient, () -> new MappingException("Missing method: " + method.getFullObfuscatedName() + " (srg: " + method.getFullDeobfuscatedName() + ")"))); + +			if (def == null) continue; + +			List<String> methodNames = CollectionUtil.map( +					namespaces, +					namespace -> "srg".equals(namespace) ? method.getDeobfuscatedName() : def.getName(namespace) +			); + +			methods.add(new TinyMethod( +					def.getDescriptor("official"), methodNames, +					/* parameters */ Collections.emptyList(), +					/* locals */ Collections.emptyList(), +					/* comments */ Collections.emptyList() +			)); +		} + +		for (FieldMapping field : klass.getFieldMappings()) { +			FieldDef def = CollectionUtil.find( +					classDef.getFields(), +					f -> f.getName("official").equals(field.getObfuscatedName()) +			).orElse(nullOrThrow(lenient, () -> new MappingException("Missing field: " + field.getFullObfuscatedName() + " (srg: " + field.getFullDeobfuscatedName() + ")"))); + +			if (def == null) continue; + +			List<String> fieldNames = CollectionUtil.map( +					namespaces, +					namespace -> "srg".equals(namespace) ? field.getDeobfuscatedName() : def.getName(namespace) +			); + +			fields.add(new TinyField(def.getDescriptor("official"), fieldNames, Collections.emptyList())); +		} + +		TinyClass tinyClass = new TinyClass(classNames, methods, fields, Collections.emptyList()); +		classConsumer.accept(tinyClass); + +		for (InnerClassMapping innerKlass : klass.getInnerClassMappings()) { +			classToTiny(foss, namespaces, innerKlass, classConsumer, lenient); +		} +	} + +	@Nullable +	private static <T, X extends Exception> T nullOrThrow(boolean lenient, 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..cdace98a --- /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) 2016, 2017, 2018 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.mapping.tree.TinyTree; + +public class SrgNamedWriter { +	public static void writeTo(Logger logger, Path srgFile, TinyTree 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/resources/idea_run_config_template.xml b/src/main/resources/idea_run_config_template.xml index 0891bc84..6b71bd71 100644 --- a/src/main/resources/idea_run_config_template.xml +++ b/src/main/resources/idea_run_config_template.xml @@ -1,5 +1,6 @@  <component name="ProjectRunConfigurationManager">    <configuration default="false" name="%NAME%" type="Application" factoryName="Application"> +    %ENVS%      <option name="MAIN_CLASS_NAME" value="%MAIN_CLASS%" />      <module name="%IDEA_MODULE%" />      <option name="PROGRAM_PARAMETERS" value="%PROGRAM_ARGS%" /> diff --git a/src/main/resources/log4j2.fabric.xml b/src/main/resources/log4j2.fabric.xml index fcfd27b5..1feeddd6 100644 --- a/src/main/resources/log4j2.fabric.xml +++ b/src/main/resources/log4j2.fabric.xml @@ -52,6 +52,7 @@  	</Appenders>  	<Loggers>  		<Logger level="${sys:fabric.log.level:-info}" name="net.minecraft"/> +		<Logger level="warn" name="cpw.mods.modlauncher.ClassTransformer"/>  		<Root level="all">  			<AppenderRef ref="DebugFile" level="${sys:fabric.log.debug.level:-debug}"/>  			<AppenderRef ref="SysOut" level="${sys:fabric.log.level:-info}"/> | 
