Improve setup speed. (#208)

* Improve access widener remapper, now uses tiny remapper

* First pass on using the new tiny remapper

* Optimise source remapping
This commit is contained in:
modmuss50 2020-05-13 19:44:45 +01:00 committed by GitHub
parent bf6fb4a95e
commit 3eff7d0fdb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 420 additions and 362 deletions

View file

@ -47,7 +47,7 @@ dependencies {
}
// tinyfile management
implementation ('net.fabricmc:tiny-remapper:0.2.2.66') {
implementation ('net.fabricmc:tiny-remapper:0.3.0.70') {
transitive = false
}

View file

@ -57,7 +57,6 @@ public class LoomGradleExtension {
public String loaderLaunchMethod;
public boolean remapMod = true;
public boolean autoGenIDERuns = true;
public boolean extractJars = false;
public String customManifest = null;
public File accessWidener = null;
public Function<String, Object> intermediaryUrl = mcVer -> "https://maven.fabricmc.net/net/fabricmc/intermediary/" + mcVer + "/intermediary-" + mcVer + "-v2.jar";

View file

@ -0,0 +1,104 @@
/*
* 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.processors.dependency;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import com.google.gson.JsonObject;
import net.fabricmc.loom.util.ModProcessor;
public class ModDependencyInfo {
public final String group;
public final String name;
public final String version;
public final String classifier;
public final File inputFile;
public final RemapData remapData;
public ModDependencyInfo(String group, String name, String version, String classifier, File inputFile, RemapData remapData) {
this.group = group;
this.name = name;
this.version = version;
this.classifier = classifier;
this.inputFile = inputFile;
this.remapData = remapData;
}
public String getRemappedNotation() {
return String.format("%s:%s:%s@%s%s", group, name, version, remapData.mappingsSuffix, classifier);
}
public String getRemappedFilename() {
return String.format("%s-%s@%s", name, version, remapData.mappingsSuffix + classifier.replace(':', '-'));
}
public File getRemappedOutput() {
return new File(remapData.modStore, getRemappedFilename() + ".jar");
}
public File getInputFile() {
return inputFile;
}
public boolean requiresRemapping() {
return !getRemappedOutput().exists() || inputFile.lastModified() <= 0 || inputFile.lastModified() > getRemappedOutput().lastModified();
}
public void finaliseRemapping() {
getRemappedOutput().setLastModified(inputFile.lastModified());
}
@Override
public String toString() {
return String.format("%s:%s:%s:%s", group, name, version, classifier);
}
public String getAccessWidener() throws IOException {
try (JarFile jarFile = new JarFile(getInputFile())) {
JarEntry modJsonEntry = jarFile.getJarEntry("fabric.mod.json");
if (modJsonEntry == null) {
return null;
}
try (InputStream inputStream = jarFile.getInputStream(modJsonEntry)) {
JsonObject json = ModProcessor.GSON.fromJson(new InputStreamReader(inputStream), JsonObject.class);
if (!json.has("accessWidener")) {
return null;
}
return json.get("accessWidener").getAsString();
}
}
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.processors.dependency;
import java.io.File;
public class RemapData {
public final String mappingsSuffix;
public final File modStore;
public RemapData(String mappingsSuffix, File modStore) {
this.mappingsSuffix = mappingsSuffix;
this.modStore = modStore;
}
}

View file

@ -25,14 +25,19 @@
package net.fabricmc.loom.providers;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Consumer;
import org.gradle.api.Project;
import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DependencyProvider;
import net.fabricmc.loom.util.MapJarsTiny;
public class MinecraftMappedProvider extends DependencyProvider {
private File minecraftMappedJar;
@ -66,7 +71,7 @@ public class MinecraftMappedProvider extends DependencyProvider {
}
try {
new MapJarsTiny().mapJars(minecraftProvider, this, this.minecraftMappedJar, this.minecraftIntermediaryJar, getProject());
mapMinecraftJar();
} catch (Throwable t) {
//Cleanup some some things that may be in a bad state now
minecraftMappedJar.delete();
@ -83,6 +88,47 @@ public class MinecraftMappedProvider extends DependencyProvider {
addDependencies(dependency, postPopulationScheduler);
}
private void mapMinecraftJar() throws IOException {
String fromM = "official";
MappingsProvider mappingsProvider = getExtension().getMappingsProvider();
Path input = minecraftProvider.getMergedJar().toPath();
Path outputMapped = minecraftMappedJar.toPath();
Path outputIntermediary = minecraftIntermediaryJar.toPath();
for (String toM : Arrays.asList("named", "intermediary")) {
Path output = "named".equals(toM) ? outputMapped : outputIntermediary;
getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")");
TinyRemapper remapper = getTinyRemapper(fromM, toM);
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) {
outputConsumer.addNonClassFiles(input);
remapper.readClassPath(getRemapClasspath());
remapper.readInputs(input);
remapper.apply(outputConsumer);
} catch (Exception e) {
throw new RuntimeException("Failed to remap JAR " + input + " with mappings from " + mappingsProvider.tinyMappings, e);
} finally {
remapper.finish();
}
}
}
public TinyRemapper getTinyRemapper(String fromM, String toM) throws IOException {
return TinyRemapper.newRemapper()
.withMappings(TinyRemapperMappingsHelper.create(getExtension().getMappingsProvider().getMappings(), fromM, toM, true))
.renameInvalidLocals(true)
.rebuildSourceFilenames(true)
.build();
}
public Path[] getRemapClasspath() {
return getMapperPaths().stream().map(File::toPath).toArray(Path[]::new);
}
protected void addDependencies(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) {
getProject().getRepositories().flatDir(repository -> repository.dir(getJarDirectory(getExtension().getUserCache(), "mapped")));

View file

@ -110,11 +110,16 @@ public class RemapJarTask extends Jar {
remapper.readInputs(input);
remapper.apply(outputConsumer);
} catch (Exception e) {
throw new RuntimeException("Failed to remap " + input + " to " + output, e);
} finally {
remapper.finish();
throw new RuntimeException("Failed to remap " + input + " to " + output, e);
}
if (extension.accessWidener != null) {
extension.getJarProcessorManager().getByType(AccessWidenerJarProcessor.class).remapAccessWidener(output, remapper.getRemapper());
}
remapper.finish();
if (!Files.exists(output)) {
throw new RuntimeException("Failed to remap " + input + " to " + output + " - file missing!");
}
@ -129,10 +134,6 @@ public class RemapJarTask extends Jar {
}
}
if (extension.accessWidener != null) {
extension.getJarProcessorManager().getByType(AccessWidenerJarProcessor.class).remapAccessWidener(output);
}
/*try {
if (modJar.exists()) {
Files.move(modJar, modJarUnmappedCopy);

View file

@ -116,11 +116,13 @@ public class LoomDependencyManager {
});
}
SourceRemapper sourceRemapper = new SourceRemapper(project, true);
{
String mappingsKey = mappingsProvider.mappingsName + "." + mappingsProvider.minecraftVersion.replace(' ', '_').replace('.', '_').replace('-', '_') + "." + mappingsProvider.mappingsVersion;
for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) {
ModCompileRemapper.remapDependencies(project, mappingsKey, extension, project.getConfigurations().getByName(entry.getSourceConfiguration()), project.getConfigurations().getByName(entry.getRemappedConfiguration()), project.getConfigurations().getByName(entry.getTargetConfiguration(project.getConfigurations())), afterTasks::add);
ModCompileRemapper.remapDependencies(project, mappingsKey, extension, project.getConfigurations().getByName(entry.getSourceConfiguration()), project.getConfigurations().getByName(entry.getRemappedConfiguration()), project.getConfigurations().getByName(entry.getTargetConfiguration(project.getConfigurations())), sourceRemapper);
}
}
@ -150,6 +152,8 @@ public class LoomDependencyManager {
project.getLogger().warn("fabric-installer.json not found in classpath!");
}
sourceRemapper.remapAll();
for (Runnable runnable : afterTasks) {
runnable.run();
}

View file

@ -1,77 +0,0 @@
/*
* 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.nio.file.Path;
import java.util.Arrays;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.providers.MappingsProvider;
import net.fabricmc.loom.providers.MinecraftMappedProvider;
import net.fabricmc.loom.providers.MinecraftProvider;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
public class MapJarsTiny {
public void mapJars(MinecraftProvider jarProvider, MinecraftMappedProvider mapProvider, File mappedJar, File intermediaryJar, Project project) throws IOException {
String fromM = "official";
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
MappingsProvider mappingsProvider = extension.getMappingsProvider();
Path[] classpath = mapProvider.getMapperPaths().stream().map(File::toPath).toArray(Path[]::new);
Path input = jarProvider.getMergedJar().toPath();
Path outputMapped = mappedJar.toPath();
Path outputIntermediary = intermediaryJar.toPath();
for (String toM : Arrays.asList("named", "intermediary")) {
Path output = "named".equals(toM) ? outputMapped : outputIntermediary;
project.getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")");
TinyRemapper remapper = TinyRemapper.newRemapper()
.withMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, true))
.renameInvalidLocals(true)
.rebuildSourceFilenames(true)
.build();
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(output).build()) {
outputConsumer.addNonClassFiles(input);
remapper.readClassPath(classpath);
remapper.readInputs(input);
remapper.apply(outputConsumer);
} catch (Exception e) {
throw new RuntimeException("Failed to remap JAR " + input + " with mappings from " + mappingsProvider.tinyMappings, e);
} finally {
remapper.finish();
}
}
}
}

View file

@ -26,7 +26,9 @@ package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;
import org.gradle.api.Project;
@ -45,12 +47,19 @@ import org.gradle.jvm.JvmLibrary;
import org.gradle.language.base.artifact.SourcesArtifact;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.processors.dependency.ModDependencyInfo;
import net.fabricmc.loom.processors.dependency.RemapData;
public class ModCompileRemapper {
public static void remapDependencies(Project project, String mappingsSuffix, LoomGradleExtension extension, Configuration modCompile, Configuration modCompileRemapped, Configuration regularCompile, Consumer<Runnable> postPopulationScheduler) {
public static void remapDependencies(Project project, String mappingsSuffix, LoomGradleExtension extension, Configuration modCompile, Configuration modCompileRemapped, Configuration regularCompile, SourceRemapper sourceRemapper) {
Logger logger = project.getLogger();
DependencyHandler dependencies = project.getDependencies();
final File modStore = extension.getRemappedModCache();
final RemapData remapData = new RemapData(mappingsSuffix, modStore);
final List<ModDependencyInfo> modDependencies = new ArrayList<>();
for (ResolvedArtifact artifact : modCompile.getResolvedConfiguration().getResolvedArtifacts()) {
String group;
String name;
@ -76,21 +85,32 @@ public class ModCompileRemapper {
File sources = findSources(dependencies, artifact);
ModDependencyInfo info = new ModDependencyInfo(group, name, version, classifierSuffix, artifact.getFile(), remapData);
modDependencies.add(info);
String remappedLog = group + ":" + name + ":" + version + classifierSuffix + " (" + mappingsSuffix + ")";
String remappedNotation = String.format("%s:%s:%s@%s%s", group, name, version, mappingsSuffix, classifierSuffix);
String remappedFilename = String.format("%s-%s@%s", name, version, mappingsSuffix + classifierSuffix.replace(':', '-'));
project.getLogger().info(":providing " + remappedLog);
File modStore = extension.getRemappedModCache();
remapArtifact(project, modCompileRemapped, artifact, remappedFilename, modStore);
project.getDependencies().add(modCompileRemapped.getName(), project.getDependencies().module(remappedNotation));
if (sources != null) {
scheduleSourcesRemapping(project, postPopulationScheduler, sources, remappedLog, remappedFilename, modStore);
scheduleSourcesRemapping(project, sourceRemapper, sources, remappedLog, remappedFilename, modStore);
}
}
List<ModDependencyInfo> processList = modDependencies.stream()
.filter(ModDependencyInfo::requiresRemapping)
.collect(Collectors.toList());
try {
ModProcessor.processMods(project, processList);
} catch (IOException e) {
throw new RuntimeException("Failed to remap mods", e);
}
// Add all of the remapped mods onto the config
for (ModDependencyInfo modDependency : modDependencies) {
project.getDependencies().add(modCompileRemapped.getName(), project.getDependencies().module(modDependency.getRemappedNotation()));
}
}
/**
@ -123,28 +143,6 @@ public class ModCompileRemapper {
dependencies.add(regularCompile.getName(), dep);
}
private static void remapArtifact(Project project, Configuration config, ResolvedArtifact artifact, String remappedFilename, File modStore) {
File input = artifact.getFile();
File output = new File(modStore, remappedFilename + ".jar");
if (!output.exists() || input.lastModified() <= 0 || input.lastModified() > output.lastModified()) {
//If the output doesn't exist, or appears to be outdated compared to the input we'll remap it
try {
ModProcessor.processMod(input, output, project, config, artifact);
} catch (IOException e) {
throw new RuntimeException("Failed to remap mod", e);
}
if (!output.exists()) {
throw new RuntimeException("Failed to remap mod");
}
output.setLastModified(input.lastModified());
} else {
project.getLogger().info(output.getName() + " is up to date with " + input.getName());
}
}
public static File findSources(DependencyHandler dependencies, ResolvedArtifact artifact) {
@SuppressWarnings("unchecked") ArtifactResolutionQuery query = dependencies.createArtifactResolutionQuery()//
.forComponents(artifact.getId().getComponentIdentifier())//
@ -161,23 +159,21 @@ public class ModCompileRemapper {
return null;
}
private static void scheduleSourcesRemapping(Project project, Consumer<Runnable> postPopulationScheduler, File sources, String remappedLog, String remappedFilename, File modStore) {
postPopulationScheduler.accept(() -> {
project.getLogger().info(":providing " + remappedLog + " sources");
File remappedSources = new File(modStore, remappedFilename + "-sources.jar");
private static void scheduleSourcesRemapping(Project project, SourceRemapper sourceRemapper, File sources, String remappedLog, String remappedFilename, File modStore) {
project.getLogger().info(":providing " + remappedLog + " sources");
File remappedSources = new File(modStore, remappedFilename + "-sources.jar");
if (!remappedSources.exists() || sources.lastModified() <= 0 || sources.lastModified() > remappedSources.lastModified()) {
try {
SourceRemapper.remapSources(project, sources, remappedSources, true);
if (!remappedSources.exists() || sources.lastModified() <= 0 || sources.lastModified() > remappedSources.lastModified()) {
try {
sourceRemapper.scheduleRemapSources(sources, remappedSources);
//Set the remapped sources creation date to match the sources if we're likely succeeded in making it
remappedSources.setLastModified(sources.lastModified());
} catch (Exception e) {
e.printStackTrace();
}
} else {
project.getLogger().info(remappedSources.getName() + " is up to date with " + sources.getName());
//Set the remapped sources creation date to match the sources if we're likely succeeded in making it
remappedSources.setLastModified(sources.lastModified());
} catch (Exception e) {
e.printStackTrace();
}
});
} else {
project.getLogger().info(remappedSources.getName() + " is up to date with " + sources.getName());
}
}
}

View file

@ -25,31 +25,28 @@
package net.fabricmc.loom.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.apache.commons.io.IOUtils;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.objectweb.asm.commons.Remapper;
import org.zeroturnaround.zip.ZipUtil;
import org.zeroturnaround.zip.commons.FileUtils;
import org.zeroturnaround.zip.transform.StringZipEntryTransformer;
import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry;
@ -58,82 +55,34 @@ import net.fabricmc.loom.providers.MappingsProvider;
import net.fabricmc.loom.providers.MinecraftMappedProvider;
import net.fabricmc.loom.util.accesswidener.AccessWidener;
import net.fabricmc.loom.util.accesswidener.AccessWidenerRemapper;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.loom.processors.dependency.ModDependencyInfo;
import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.InputTag;
import net.fabricmc.tinyremapper.OutputConsumerPath;
public class ModProcessor {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
public static void processMod(File input, File output, Project project, Configuration config, ResolvedArtifact artifact) throws IOException {
if (output.exists()) {
output.delete();
public static void processMods(Project project, List<ModDependencyInfo> processList) throws IOException {
if (processList.isEmpty()) {
return;
}
remapJar(input, output, project, artifact);
//Enable this if you want your nested jars to be extracted, this will extract **all** jars
if (project.getExtensions().getByType(LoomGradleExtension.class).extractJars) {
handleNestedJars(input, project, config, artifact);
}
remapaccessWidener(output, project);
//Always strip the nested jars
stripNestedJars(output);
}
private static void handleNestedJars(File input, Project project, Configuration config, ResolvedArtifact artifact) throws IOException {
try (JarFile jarFile = new JarFile(input)) {
JarEntry modJsonEntry = jarFile.getJarEntry("fabric.mod.json");
if (modJsonEntry == null) {
return;
}
try (InputStream inputStream = jarFile.getInputStream(modJsonEntry)) {
JsonObject json = GSON.fromJson(new InputStreamReader(inputStream), JsonObject.class);
if (json == null || !json.has("jars")) {
return;
}
JsonArray jsonArray = json.getAsJsonArray("jars");
for (int i = 0; i < jsonArray.size(); i++) {
JsonObject jsonObject = jsonArray.get(i).getAsJsonObject();
String fileName = jsonObject.get("file").getAsString();
project.getLogger().lifecycle(String.format("Found %s nested in %s", fileName, input.getName()));
processNestedJar(jarFile, fileName, project, config, artifact);
}
for (ModDependencyInfo info : processList) {
if (info.getRemappedOutput().exists()) {
info.getRemappedOutput().delete();
}
}
}
private static void processNestedJar(JarFile parentJar, String fileName, Project project, Configuration config, ResolvedArtifact artifact) throws IOException {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
remapJars(project, processList);
JarEntry entry = parentJar.getJarEntry(fileName);
for (ModDependencyInfo info : processList) {
if (!info.getRemappedOutput().exists()) {
throw new RuntimeException("Failed to remap mod" + info);
}
if (entry == null) {
throw new RuntimeException(String.format("%s was not found in %s", fileName, parentJar.getName()));
stripNestedJars(info.getRemappedOutput());
}
File nestedFile = new File(extension.getNestedModCache(), fileName.substring(fileName.lastIndexOf("/")));
try (InputStream jarStream = parentJar.getInputStream(entry)) {
FileUtils.copy(jarStream, nestedFile);
}
File remappedFile = new File(extension.getRemappedModCache(), fileName.substring(fileName.lastIndexOf("/")));
processMod(nestedFile, remappedFile, project, config, artifact);
if (!remappedFile.exists()) {
throw new RuntimeException("Failed to find processed nested jar");
}
//Add the project right onto the remapped mods, hopefully this works
project.getDependencies().add(config.getName(), project.files(remappedFile));
}
private static void stripNestedJars(File file) {
@ -148,57 +97,24 @@ public class ModProcessor {
}))});
}
private static void remapaccessWidener(File input, Project project) throws IOException {
String accessWidenerPath;
try (JarFile jarFile = new JarFile(input)) {
JarEntry modJsonEntry = jarFile.getJarEntry("fabric.mod.json");
if (modJsonEntry == null) {
return;
}
try (InputStream inputStream = jarFile.getInputStream(modJsonEntry)) {
JsonObject json = GSON.fromJson(new InputStreamReader(inputStream), JsonObject.class);
if (!json.has("accessWidener")) {
return;
}
accessWidenerPath = json.get("accessWidener").getAsString();
}
}
if (accessWidenerPath == null) {
return;
}
ZipUtil.transformEntry(input, accessWidenerPath, new StringZipEntryTransformer() {
@Override
protected String transform(ZipEntry zipEntry, String input) throws IOException {
return remapaccessWidener(input, project);
}
});
}
private static String remapaccessWidener(String input, Project project) {
try (BufferedReader bufferedReader = new BufferedReader(new StringReader(input))) {
private static byte[] remapaccessWidener(byte[] input, Remapper remapper) {
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(input), StandardCharsets.UTF_8))) {
AccessWidener accessWidener = new AccessWidener();
accessWidener.read(bufferedReader);
AccessWidenerRemapper accessWidenerRemapper = new AccessWidenerRemapper(accessWidener, project.getExtensions().getByType(LoomGradleExtension.class).getMappingsProvider().getMappings(), "named");
AccessWidenerRemapper accessWidenerRemapper = new AccessWidenerRemapper(accessWidener, remapper, "named");
AccessWidener remapped = accessWidenerRemapper.remap();
try (StringWriter writer = new StringWriter()) {
remapped.write(writer);
return writer.toString();
return writer.toString().getBytes(StandardCharsets.UTF_8);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void remapJar(File input, File output, Project project, ResolvedArtifact artifact) throws IOException {
private static void remapJars(Project project, List<ModDependencyInfo> processList) throws IOException {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
String fromM = "intermediary";
String toM = "named";
@ -206,47 +122,57 @@ public class ModProcessor {
MinecraftMappedProvider mappedProvider = extension.getMinecraftMappedProvider();
MappingsProvider mappingsProvider = extension.getMappingsProvider();
Path inputPath = input.getAbsoluteFile().toPath();
Path mc = mappedProvider.getIntermediaryJar().toPath();
Path[] mcDeps = mappedProvider.getMapperPaths().stream().map(File::toPath).toArray(Path[]::new);
Set<Path> modCompiles = new HashSet<>();
for (RemappedConfigurationEntry entry : Constants.MOD_COMPILE_ENTRIES) {
project.getConfigurations().getByName(entry.getSourceConfiguration()).getFiles().stream().filter((f) -> !f.equals(input)).map(p -> {
if (p.equals(input)) {
return inputPath;
} else {
return p.toPath();
}
}).forEach(modCompiles::add);
}
project.getLogger().lifecycle(":remapping " + input.getName() + " (TinyRemapper, " + fromM + " -> " + toM + ")");
// If the sources don't exist, we want remapper to give nicer names to the missing variable names.
// However, if the sources do exist, if remapper gives names to the parameters that prevents IDEs (at least IDEA)
// from replacing the parameters with the actual names from the sources.
boolean sourcesExist = ModCompileRemapper.findSources(project.getDependencies(), artifact) != null;
project.getLogger().lifecycle(":remapping " + processList.size() + " mods (TinyRemapper, " + fromM + " -> " + toM + ")");
TinyRemapper remapper = TinyRemapper.newRemapper()
.withMappings(TinyRemapperMappingsHelper.create(mappingsProvider.getMappings(), fromM, toM, false))
.renameInvalidLocals(!sourcesExist)
.renameInvalidLocals(false)
.build();
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(Paths.get(output.getAbsolutePath())).build()) {
outputConsumer.addNonClassFiles(inputPath);
remapper.readClassPath(modCompiles.toArray(new Path[0]));
remapper.readClassPath(mc);
remapper.readClassPath(mcDeps);
remapper.readInputs(inputPath);
remapper.apply(outputConsumer);
} finally {
remapper.finish();
remapper.readClassPathAsync(mc);
remapper.readClassPathAsync(mcDeps);
final Map<ModDependencyInfo, InputTag> tagMap = new HashMap<>();
final Map<ModDependencyInfo, OutputConsumerPath> outputConsumerMap = new HashMap<>();
for (ModDependencyInfo info : processList) {
InputTag tag = remapper.createInputTag();
remapper.readInputsAsync(tag, info.getInputFile().toPath());
tagMap.put(info, tag);
}
if (!output.exists()) {
throw new RuntimeException("Failed to remap JAR to " + toM + " file not found: " + output.getAbsolutePath());
// Apply this in a second loop as we need to ensure all the inputs are on the classpath before remapping.
for (ModDependencyInfo info : processList) {
OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(info.getRemappedOutput().toPath()).build();
outputConsumer.addNonClassFiles(info.getInputFile().toPath());
outputConsumerMap.put(info, outputConsumer);
String accessWidener = info.getAccessWidener();
if (accessWidener == null) {
remapper.apply(outputConsumer, tagMap.get(info));
} else {
remapper.apply(remapAccessWidener(remapper.getRemapper(), accessWidener, outputConsumer), tagMap.get(info));
}
}
remapper.finish();
for (ModDependencyInfo info : processList) {
outputConsumerMap.get(info).close();
}
}
static BiConsumer<String, byte[]> remapAccessWidener(Remapper remapper, String accessWidener, BiConsumer<String, byte[]> output) {
return (s, bytes) -> {
if (s.equals(accessWidener)) {
output.accept(s, remapaccessWidener(bytes, remapper));
} else {
output.accept(s, bytes);
}
};
}
static JsonObject readInstallerJson(File file, Project project) {

View file

@ -28,6 +28,8 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.lorenz.io.MappingsReader;
@ -46,44 +48,46 @@ import net.fabricmc.mapping.tree.TinyTree;
import net.fabricmc.stitch.util.StitchUtil;
public class SourceRemapper {
public static void remapSources(Project project, File source, File destination, boolean toNamed) throws Exception {
remapSourcesInner(project, source, destination, toNamed);
private final Project project;
private final boolean toNamed;
private final List<Runnable> remapTasks = new ArrayList<>();
private Mercury mercury;
public SourceRemapper(Project project, boolean toNamed) {
this.project = project;
this.toNamed = toNamed;
}
public static void remapSources(Project project, File input, File output, boolean named) throws Exception {
SourceRemapper sourceRemapper = new SourceRemapper(project, named);
sourceRemapper.scheduleRemapSources(input, output);
sourceRemapper.remapAll();
}
public void scheduleRemapSources(File source, File destination) throws Exception {
remapTasks.add(() -> {
try {
remapSourcesInner(source, destination);
} catch (Exception e) {
throw new RuntimeException("Failed to remap sources for " + source, e);
}
});
}
public void remapAll() {
if (!remapTasks.isEmpty()) {
project.getLogger().lifecycle(":remapping sources");
}
remapTasks.forEach(Runnable::run);
// TODO: FIXME - WORKAROUND https://github.com/FabricMC/fabric-loom/issues/45
System.gc();
}
private static void remapSourcesInner(Project project, File source, File destination, boolean toNamed) throws Exception {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
MappingsProvider mappingsProvider = extension.getMappingsProvider();
MappingSet mappings = extension.getOrCreateSrcMappingCache(toNamed ? 1 : 0, () -> {
try {
TinyTree m = mappingsProvider.getMappings();
project.getLogger().lifecycle(":loading " + (toNamed ? "intermediary -> named" : "named -> intermediary") + " source mappings");
return new TinyReader(m, toNamed ? "intermediary" : "named", toNamed ? "named" : "intermediary").read();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
private void remapSourcesInner(File source, File destination) throws Exception {
project.getLogger().info(":remapping source jar");
Mercury mercury = extension.getOrCreateSrcMercuryCache(toNamed ? 1 : 0, () -> {
Mercury m = createMercuryWithClassPath(project, toNamed);
for (Path file : extension.getUnmappedMods()) {
if (Files.isRegularFile(file)) {
m.getClassPath().add(file);
}
}
m.getClassPath().add(extension.getMinecraftMappedProvider().getMappedJar().toPath());
m.getClassPath().add(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath());
m.getProcessors().add(MercuryRemapper.create(mappings));
return m;
});
Mercury mercury = getMercuryInstance();
if (source.equals(destination)) {
if (source.isDirectory()) {
@ -135,6 +139,45 @@ public class SourceRemapper {
}
}
private Mercury getMercuryInstance() {
if (this.mercury != null) {
return this.mercury;
}
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
MappingsProvider mappingsProvider = extension.getMappingsProvider();
MappingSet mappings = extension.getOrCreateSrcMappingCache(toNamed ? 1 : 0, () -> {
try {
TinyTree m = mappingsProvider.getMappings();
project.getLogger().lifecycle(":loading " + (toNamed ? "intermediary -> named" : "named -> intermediary") + " source mappings");
return new TinyReader(m, toNamed ? "intermediary" : "named", toNamed ? "named" : "intermediary").read();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
Mercury mercury = extension.getOrCreateSrcMercuryCache(toNamed ? 1 : 0, () -> {
Mercury m = createMercuryWithClassPath(project, toNamed);
for (Path file : extension.getUnmappedMods()) {
if (Files.isRegularFile(file)) {
m.getClassPath().add(file);
}
}
m.getClassPath().add(extension.getMinecraftMappedProvider().getMappedJar().toPath());
m.getClassPath().add(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath());
m.getProcessors().add(MercuryRemapper.create(mappings));
return m;
});
this.mercury = mercury;
return mercury;
}
private static void copyNonJavaFiles(Path from, Path to, Project project, File source) throws IOException {
Files.walk(from).forEach(path -> {
Path targetPath = to.resolve(from.relativize(path).toString());

View file

@ -44,6 +44,7 @@ import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.Remapper;
import org.zeroturnaround.zip.ZipUtil;
import org.zeroturnaround.zip.transform.ByteArrayZipEntryTransformer;
import org.zeroturnaround.zip.transform.ZipEntryTransformer;
@ -53,6 +54,7 @@ import net.fabricmc.mappings.EntryTriple;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.processors.JarProcessor;
import net.fabricmc.tinyremapper.TinyRemapper;
public class AccessWidenerJarProcessor implements JarProcessor {
private AccessWidener accessWidener = new AccessWidener();
@ -79,8 +81,13 @@ public class AccessWidenerJarProcessor implements JarProcessor {
//Remap accessWidener if its not named, allows for AE's to be written in intermediary
if (!accessWidener.namespace.equals("named")) {
try {
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, loomGradleExtension.getMappingsProvider().getMappings(), "named");
TinyRemapper tinyRemapper = loomGradleExtension.getMinecraftMappedProvider().getTinyRemapper("official", "named");
tinyRemapper.readClassPath(loomGradleExtension.getMinecraftMappedProvider().getRemapClasspath());
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, tinyRemapper.getRemapper(), "named");
accessWidener = remapper.remap();
tinyRemapper.finish();
} catch (IOException e) {
throw new RuntimeException("Failed to remap access widener", e);
}
@ -116,9 +123,8 @@ public class AccessWidenerJarProcessor implements JarProcessor {
}
//Called when remapping the mod
public void remapAccessWidener(Path modJarPath) throws IOException {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, extension.getMappingsProvider().getMappings(), "intermediary");
public void remapAccessWidener(Path modJarPath, Remapper asmRemapper) throws IOException {
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, asmRemapper, "intermediary");
AccessWidener remapped = remapper.remap();
StringWriter writer = new StringWriter();

View file

@ -24,54 +24,21 @@
package net.fabricmc.loom.util.accesswidener;
import java.util.HashMap;
import java.util.Map;
import net.fabricmc.mapping.tree.ClassDef;
import net.fabricmc.mapping.tree.FieldDef;
import net.fabricmc.mapping.tree.MethodDef;
import net.fabricmc.mapping.tree.TinyTree;
import org.objectweb.asm.commons.Remapper;
import net.fabricmc.mappings.EntryTriple;
public class AccessWidenerRemapper {
private final AccessWidener input;
private final String from, to;
private final String to;
private final Remapper remapper;
private Map<String, String> classNames = new HashMap<>();
private Map<EntryTriple, EntryTriple> fieldNames = new HashMap<>();
private Map<EntryTriple, EntryTriple> methodNames = new HashMap<>();
public AccessWidenerRemapper(AccessWidener input, TinyTree tinyTree, String to) {
public AccessWidenerRemapper(AccessWidener input, Remapper remapper, String to) {
this.input = input;
this.from = input.namespace;
this.to = to;
populateMappings(tinyTree);
}
private void populateMappings(TinyTree tinyTree) {
if (!tinyTree.getMetadata().getNamespaces().contains(from)) {
throw new UnsupportedOperationException("Unknown namespace: " + from);
}
if (!tinyTree.getMetadata().getNamespaces().contains(to)) {
throw new UnsupportedOperationException("Unknown namespace: " + to);
}
for (ClassDef classDef : tinyTree.getClasses()) {
classNames.put(classDef.getName(from), classDef.getName(to));
for (FieldDef fieldDef : classDef.getFields()) {
EntryTriple fromEntry = new EntryTriple(classDef.getName(from), fieldDef.getName(from), fieldDef.getDescriptor(from));
EntryTriple toEntry = new EntryTriple(classDef.getName(to), fieldDef.getName(to), fieldDef.getDescriptor(to));
fieldNames.put(fromEntry, toEntry);
}
for (MethodDef methodDef : classDef.getMethods()) {
EntryTriple fromEntry = new EntryTriple(classDef.getName(from), methodDef.getName(from), methodDef.getDescriptor(from));
EntryTriple toEntry = new EntryTriple(classDef.getName(to), methodDef.getName(to), methodDef.getDescriptor(to));
methodNames.put(fromEntry, toEntry);
}
}
this.remapper = remapper;
}
public AccessWidener remap() {
@ -84,27 +51,33 @@ public class AccessWidenerRemapper {
remapped.namespace = to;
for (Map.Entry<String, AccessWidener.Access> entry : input.classAccess.entrySet()) {
remapped.classAccess.put(findMapping(classNames, entry.getKey()), entry.getValue());
remapped.classAccess.put(remapper.map(entry.getKey()), entry.getValue());
}
for (Map.Entry<EntryTriple, AccessWidener.Access> entry : input.methodAccess.entrySet()) {
remapped.addOrMerge(remapped.methodAccess, findMapping(methodNames, entry.getKey()), entry.getValue());
remapped.addOrMerge(remapped.methodAccess, remapMethod(entry.getKey()), entry.getValue());
}
for (Map.Entry<EntryTriple, AccessWidener.Access> entry : input.fieldAccess.entrySet()) {
remapped.addOrMerge(remapped.fieldAccess, findMapping(fieldNames, entry.getKey()), entry.getValue());
remapped.addOrMerge(remapped.fieldAccess, remapField(entry.getKey()), entry.getValue());
}
return remapped;
}
private static <K, V> V findMapping(Map<K, V> map, K key) {
V value = map.get(key);
private EntryTriple remapMethod(EntryTriple entryTriple) {
return new EntryTriple(
remapper.map(entryTriple.getName()),
remapper.mapMethodName(entryTriple.getOwner(), entryTriple.getName(), entryTriple.getDesc()),
remapper.mapDesc(entryTriple.getDesc())
);
}
if (value == null) {
throw new RuntimeException("Failed to find mapping for " + key.toString());
}
return value;
private EntryTriple remapField(EntryTriple entryTriple) {
return new EntryTriple(
remapper.map(entryTriple.getName()),
remapper.mapFieldName(entryTriple.getOwner(), entryTriple.getName(), entryTriple.getDesc()),
remapper.mapDesc(entryTriple.getDesc())
);
}
}