Fix performance regressions in large multi-project builds. (#571)

* Perf improvements to multi-project builds.

* Fixes.

* More fixes.

* Layered mappings fixes

* Perf improvements. Undo broken fix.

* Fix remap classpath being empty.

* Another gradle bug? Either way this is fine and works.

* Fix broken test

* Final fixes?

* Fix and cleanup mixin ap mappings.
dev/0.11
modmuss50 2022-01-14 19:50:45 +00:00 committed by GitHub
parent 6fd3d5d021
commit d40241d75a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1179 additions and 416 deletions

View File

@ -114,14 +114,8 @@ jar {
from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) } from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) }
} }
task sourcesJar(type: Jar, dependsOn: classes) { java {
archiveClassifier = 'sources' withSourcesJar()
from sourceSets.main.allSource
}
task javadocJar(type: Jar, dependsOn: javadoc) {
archiveClassifier = 'javadoc'
from javadoc.destinationDir
} }
spotless { spotless {
@ -197,10 +191,7 @@ publishing {
artifactId project.archivesBaseName artifactId project.archivesBaseName
version project.version version project.version
from components['java'] from components.java
artifact sourcesJar
artifact javadocJar
pom.withXml { pom.withXml {
patchPom(asNode()) patchPom(asNode())
@ -213,10 +204,7 @@ publishing {
artifactId project.archivesBaseName artifactId project.archivesBaseName
version baseVersion + '-SNAPSHOT' version baseVersion + '-SNAPSHOT'
from components['java'] from components.java
artifact sourcesJar
artifact javadocJar
pom.withXml { pom.withXml {
patchPom(asNode()) patchPom(asNode())

View File

@ -24,7 +24,6 @@
package net.fabricmc.loom; package net.fabricmc.loom;
import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -37,7 +36,6 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.LoomGradleExtensionAPI;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
@ -112,10 +110,6 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace); FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace);
File getMixinMappings(SourceSet sourceSet);
FileCollection getAllMixinMappings();
boolean isRootProject(); boolean isRootProject();
default String getIntermediaryUrl(String minecraftVersion) { default String getIntermediaryUrl(String minecraftVersion) {

View File

@ -37,7 +37,7 @@ public abstract class DecompilerOptions implements Named {
/** /**
* Class name for to the {@link LoomDecompiler}. * Class name for to the {@link LoomDecompiler}.
*/ */
public abstract Property<String> getDecompilerClassname(); public abstract Property<String> getDecompilerClassName();
/** /**
* Additional classpath entries for the decompiler jvm. * Additional classpath entries for the decompiler jvm.
@ -60,7 +60,7 @@ public abstract class DecompilerOptions implements Named {
public abstract Property<Integer> getMaxThreads(); public abstract Property<Integer> getMaxThreads();
public DecompilerOptions() { public DecompilerOptions() {
getDecompilerClassname().finalizeValueOnRead(); getDecompilerClassName().finalizeValueOnRead();
getClasspath().finalizeValueOnRead(); getClasspath().finalizeValueOnRead();
getOptions().finalizeValueOnRead(); getOptions().finalizeValueOnRead();
getMemory().convention(4096L).finalizeValueOnRead(); getMemory().convention(4096L).finalizeValueOnRead();
@ -71,9 +71,9 @@ public abstract class DecompilerOptions implements Named {
public record Dto(String className, Map<String, String> options, int maxThreads) implements Serializable { } public record Dto(String className, Map<String, String> options, int maxThreads) implements Serializable { }
public Dto toDto() { public Dto toDto() {
Preconditions.checkArgument(getDecompilerClassname().isPresent(), "No decompiler classname specified for decompiler: " + getName()); Preconditions.checkArgument(getDecompilerClassName().isPresent(), "No decompiler classname specified for decompiler: " + getName());
return new Dto( return new Dto(
getDecompilerClassname().get(), getDecompilerClassName().get(),
getOptions().get(), getOptions().get(),
getMaxThreads().get() getMaxThreads().get()
); );

View File

@ -25,13 +25,14 @@
package net.fabricmc.loom.api.mappings.layered; package net.fabricmc.loom.api.mappings.layered;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.function.Supplier;
import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.Dependency;
import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logger;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@ApiStatus.Experimental /* Very Experimental and not cleanly separated from the impl atm */ @ApiStatus.Experimental /* Very Experimental and not cleanly separated from the impl atm */
public interface MappingContext { public interface MappingContext {
@ -39,7 +40,7 @@ public interface MappingContext {
Path resolveMavenDependency(String mavenNotation); Path resolveMavenDependency(String mavenNotation);
MappingsProvider mappingsProvider(); Supplier<MemoryMappingTree> intermediaryTree();
MinecraftProvider minecraftProvider(); MinecraftProvider minecraftProvider();

View File

@ -42,6 +42,7 @@ import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; import net.fabricmc.loom.configuration.ide.idea.IdeaUtils;
import net.fabricmc.loom.extension.MixinExtension; import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.MixinMappingsService;
import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Constants;
/** /**
@ -84,7 +85,7 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get(); String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get();
Map<String, String> args = new HashMap<>() {{ Map<String, String> args = new HashMap<>() {{
put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, loom.getMappingsProvider().tinyMappings.toFile().getCanonicalPath()); put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, loom.getMappingsProvider().tinyMappings.toFile().getCanonicalPath());
put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, loom.getMixinMappings(sourceSet).getCanonicalPath()); put(Constants.MixinArguments.OUT_MAP_FILE_NAMED_INTERMEDIARY, MixinMappingsService.getMixinMappingFile(project, sourceSet).getCanonicalPath());
put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName)); put(Constants.MixinArguments.OUT_REFMAP_FILE, getRefmapDestination(task, refmapName));
put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary"); put(Constants.MixinArguments.DEFAULT_OBFUSCATION_ENV, "named:intermediary");
}}; }};

View File

@ -193,15 +193,15 @@ public final class CompileConfiguration {
boolean split = project.getProperties().get("fabric.loom.experimental.splitMcJars") != null; boolean split = project.getProperties().get("fabric.loom.experimental.splitMcJars") != null;
// Provide the vanilla mc jars // Provide the vanilla mc jars -- TODO share across projects.
final MinecraftProvider minecraftProvider = split ? new SplitMinecraftProvider(project) : new MergedMinecraftProvider(project); final MinecraftProvider minecraftProvider = split ? new SplitMinecraftProvider(project) : new MergedMinecraftProvider(project);
extension.setMinecraftProvider(minecraftProvider); extension.setMinecraftProvider(minecraftProvider);
minecraftProvider.provide(); minecraftProvider.provide();
// Provide the mappings final DependencyInfo mappingsDep = DependencyInfo.create(project, Constants.Configurations.MAPPINGS);
final MappingsProviderImpl mappingsProvider = new MappingsProviderImpl(project, minecraftProvider); final MappingsProviderImpl mappingsProvider = MappingsProviderImpl.getInstance(project, mappingsDep, minecraftProvider);
extension.setMappingsProvider(mappingsProvider); extension.setMappingsProvider(mappingsProvider);
mappingsProvider.provide(); mappingsProvider.applyToProject(project, mappingsDep);
// Provide the remapped mc jars // Provide the remapped mc jars
final IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider; final IntermediaryMinecraftProvider<?> intermediaryMinecraftProvider;

View File

@ -26,6 +26,7 @@ package net.fabricmc.loom.configuration.providers.mappings;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.function.Supplier;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
@ -36,6 +37,7 @@ import org.gradle.api.logging.Logger;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingContext; import net.fabricmc.loom.api.mappings.layered.MappingContext;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class GradleMappingContext implements MappingContext { public class GradleMappingContext implements MappingContext {
private final Project project; private final Project project;
@ -62,8 +64,8 @@ public class GradleMappingContext implements MappingContext {
} }
@Override @Override
public MappingsProvider mappingsProvider() { public Supplier<MemoryMappingTree> intermediaryTree() {
return extension.getMappingsProvider(); return () -> IntermediaryService.getInstance(project, minecraftProvider()).getMemoryMappingTree();
} }
@Override @Override

View File

@ -0,0 +1,118 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.mappings;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.net.UrlEscapers;
import org.gradle.api.Project;
import org.jetbrains.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.format.Tiny2Reader;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class IntermediaryService implements SharedService {
private static final Logger LOGGER = LoggerFactory.getLogger(IntermediaryService.class);
private final Path intermediaryTiny;
private final Supplier<MemoryMappingTree> memoryMappingTree = Suppliers.memoize(this::createMemoryMappingTree);
private IntermediaryService(Path intermediaryTiny) {
this.intermediaryTiny = intermediaryTiny;
}
public static synchronized IntermediaryService getInstance(Project project, MinecraftProvider minecraftProvider) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(minecraftProvider.minecraftVersion());
final String intermediaryArtifactUrl = extension.getIntermediaryUrl(encodedMinecraftVersion);
return SharedServiceManager.get(project).getOrCreateService("IntermediaryService:" + intermediaryArtifactUrl,
() -> create(intermediaryArtifactUrl, minecraftProvider));
}
@VisibleForTesting
public static IntermediaryService create(String intermediaryUrl, MinecraftProvider minecraftProvider) {
final Path intermediaryTiny = minecraftProvider.file("intermediary-v2.tiny").toPath();
if (!Files.exists(intermediaryTiny) || LoomGradlePlugin.refreshDeps) {
// Download and extract intermediary
File intermediaryJar = minecraftProvider.file("intermediary-v2.jar");
try {
DownloadUtil.downloadIfChanged(new URL(intermediaryUrl), intermediaryJar, LOGGER);
MappingsProviderImpl.extractMappings(intermediaryJar.toPath(), intermediaryTiny);
} catch (IOException e) {
throw new UncheckedIOException("Failed to download and extract intermediary", e);
}
}
return new IntermediaryService(intermediaryTiny);
}
private MemoryMappingTree createMemoryMappingTree() {
final MemoryMappingTree tree = new MemoryMappingTree();
try {
MappingNsCompleter nsCompleter = new MappingNsCompleter(tree, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true);
try (BufferedReader reader = Files.newBufferedReader(getIntermediaryTiny(), StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, nsCompleter);
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to read intermediary mappings", e);
}
return tree;
}
public MemoryMappingTree getMemoryMappingTree() {
return memoryMappingTree.get();
}
public Path getIntermediaryTiny() {
return Objects.requireNonNull(intermediaryTiny, "Intermediary mappings have not been setup");
}
}

View File

@ -28,107 +28,135 @@ import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.net.URL; import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern; import java.util.function.Supplier;
import com.google.common.base.Stopwatch; import com.google.common.base.Suppliers;
import com.google.common.net.UrlEscapers;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import org.apache.tools.ant.util.StringUtils; import org.apache.tools.ant.util.StringUtils;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.DependencyInfo; import net.fabricmc.loom.configuration.DependencyInfo;
import net.fabricmc.loom.configuration.providers.mappings.tiny.MappingsMerger;
import net.fabricmc.loom.configuration.providers.mappings.tiny.TinyJarInfo;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.DeletingFileVisitor; import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.DownloadUtil;
import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.MappingFormat; import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.format.Tiny2Reader;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.stitch.Command; import net.fabricmc.stitch.Command;
import net.fabricmc.stitch.commands.CommandProposeFieldNames; import net.fabricmc.stitch.commands.CommandProposeFieldNames;
public class MappingsProviderImpl implements MappingsProvider { public class MappingsProviderImpl implements MappingsProvider, SharedService {
public String mappingsIdentifier; private static final Logger LOGGER = LoggerFactory.getLogger(MappingsProviderImpl.class);
private Path mappingsWorkingDir; private Supplier<MemoryMappingTree> mappingTree;
private Path intermediaryTiny; public final String mappingsIdentifier;
private boolean hasRefreshed = false;
private final Path mappingsWorkingDir;
// The mappings that gradle gives us // The mappings that gradle gives us
private Path baseTinyMappings; private final Path baseTinyMappings;
// The mappings we use in practice // The mappings we use in practice
public Path tinyMappings; public final Path tinyMappings;
public Path tinyMappingsJar; public final Path tinyMappingsJar;
private Path unpickDefinitions; private final Path unpickDefinitions;
private boolean hasUnpickDefinitions; private boolean hasUnpickDefinitions;
private UnpickMetadata unpickMetadata; private UnpickMetadata unpickMetadata;
private MemoryMappingTree mappingTree;
private Map<String, String> signatureFixes; private Map<String, String> signatureFixes;
private final Project project; private final Supplier<IntermediaryService> intermediaryService;
private final MinecraftProvider minecraftProvider;
private final LoomGradleExtension extension; private MappingsProviderImpl(String mappingsIdentifier, Path mappingsWorkingDir, Supplier<IntermediaryService> intermediaryService) {
public MappingsProviderImpl(Project project, MinecraftProvider minecraftProvider) { this.mappingsIdentifier = mappingsIdentifier;
this.project = project;
this.minecraftProvider = minecraftProvider; this.mappingsWorkingDir = mappingsWorkingDir;
this.extension = LoomGradleExtension.get(project); this.baseTinyMappings = mappingsWorkingDir.resolve("mappings-base.tiny");
this.tinyMappings = mappingsWorkingDir.resolve("mappings.tiny");
this.tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar");
this.unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick");
this.intermediaryService = intermediaryService;
}
public static synchronized MappingsProviderImpl getInstance(Project project, DependencyInfo dependency, MinecraftProvider minecraftProvider) {
return SharedServiceManager.get(project).getOrCreateService("MappingsProvider:%s:%s".formatted(dependency.getDepString(), minecraftProvider.minecraftVersion()), () -> {
Supplier<IntermediaryService> intermediaryService = Suppliers.memoize(() -> IntermediaryService.getInstance(project, minecraftProvider));
return create(dependency, minecraftProvider, intermediaryService);
});
} }
public MemoryMappingTree getMappings() throws IOException { public MemoryMappingTree getMappings() throws IOException {
return Objects.requireNonNull(mappingTree, "Cannot get mappings before they have been read"); return Objects.requireNonNull(mappingTree, "Cannot get mappings before they have been read").get();
} }
public void provide() throws Exception { private static MappingsProviderImpl create(DependencyInfo dependency, MinecraftProvider minecraftProvider, Supplier<IntermediaryService> intermediaryService) {
final DependencyInfo dependency = DependencyInfo.create(project, Constants.Configurations.MAPPINGS); final String version = dependency.getResolvedVersion();
final Path inputJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve mappings: " + dependency)).toPath();
final String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged");
project.getLogger().info(":setting up mappings (" + dependency.getDependency().getName() + " " + dependency.getResolvedVersion() + ")"); final TinyJarInfo jarInfo = TinyJarInfo.get(inputJar);
jarInfo.minecraftVersionId().ifPresent(id -> {
if (!minecraftProvider.minecraftVersion().equals(id)) {
LOGGER.warn("The mappings (%s) were not build for minecraft version (%s) produce with caution.".formatted(dependency.getDepString(), minecraftProvider.minecraftVersion()));
}
});
String version = dependency.getResolvedVersion(); final String mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, jarInfo.v2()), minecraftProvider.minecraftVersion());
File mappingsJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not find yarn mappings: " + dependency)); final Path workingDir = minecraftProvider.dir(mappingsIdentifier).toPath();
String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged"); var mappingProvider = new MappingsProviderImpl(mappingsIdentifier, workingDir, intermediaryService);
boolean isV2 = isV2(dependency, mappingsJar);
this.mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, isV2));
initFiles(); try {
mappingProvider.setup(minecraftProvider, inputJar);
} catch (IOException e) {
cleanWorkingDirectory(workingDir);
throw new UncheckedIOException("Failed to setup mappings: " + dependency.getDepString(), e);
}
return mappingProvider;
}
private void setup(MinecraftProvider minecraftProvider, Path inputJar) throws IOException {
if (isRefreshDeps()) {
cleanWorkingDirectory(mappingsWorkingDir);
}
if (Files.notExists(tinyMappings) || isRefreshDeps()) { if (Files.notExists(tinyMappings) || isRefreshDeps()) {
storeMappings(project, minecraftProvider, mappingsJar.toPath()); storeMappings(minecraftProvider, inputJar);
} else { } else {
try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) { try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) {
extractExtras(fileSystem); extractExtras(fileSystem);
} }
} }
mappingTree = readMappings();
if (Files.notExists(tinyMappingsJar) || isRefreshDeps()) { if (Files.notExists(tinyMappingsJar) || isRefreshDeps()) {
Files.deleteIfExists(tinyMappingsJar); Files.deleteIfExists(tinyMappingsJar);
ZipUtils.add(tinyMappingsJar, "mappings/mappings.tiny", Files.readAllBytes(tinyMappings)); ZipUtils.add(tinyMappingsJar, "mappings/mappings.tiny", Files.readAllBytes(tinyMappings));
} }
mappingTree = Suppliers.memoize(this::readMappings);
}
public void applyToProject(Project project, DependencyInfo dependency) {
if (hasUnpickDefinitions()) { if (hasUnpickDefinitions()) {
String notation = String.format("%s:%s:%s:constants", String notation = String.format("%s:%s:%s:constants",
dependency.getDependency().getGroup(), dependency.getDependency().getGroup(),
@ -137,13 +165,13 @@ public class MappingsProviderImpl implements MappingsProvider {
); );
project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation); project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation);
populateUnpickClasspath(); populateUnpickClasspath(project);
} }
project.getDependencies().add(Constants.Configurations.MAPPINGS_FINAL, project.files(tinyMappingsJar.toFile())); project.getDependencies().add(Constants.Configurations.MAPPINGS_FINAL, project.files(tinyMappingsJar.toFile()));
} }
private String getMappingsClassifier(DependencyInfo dependency, boolean isV2) { private static String getMappingsClassifier(DependencyInfo dependency, boolean isV2) {
String[] depStringSplit = dependency.getDepString().split(":"); String[] depStringSplit = dependency.getDepString().split(":");
if (depStringSplit.length >= 4) { if (depStringSplit.length >= 4) {
@ -153,42 +181,22 @@ public class MappingsProviderImpl implements MappingsProvider {
return isV2 ? "-v2" : ""; return isV2 ? "-v2" : "";
} }
private boolean isV2(DependencyInfo dependency, File mappingsJar) throws IOException { private void storeMappings(MinecraftProvider minecraftProvider, Path inputJar) throws IOException {
String minecraftVersion = minecraftProvider.minecraftVersion(); LOGGER.info(":extracting " + inputJar.getFileName());
// Only do this for official yarn, there isn't really a way we can get the mc version for all mappings try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) {
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();
char separator = yarnVersion.contains("+build.") ? '+' : yarnVersion.contains("-") ? '-' : '.';
String yarnMinecraftVersion = yarnVersion.substring(0, yarnVersion.lastIndexOf(separator));
if (!yarnMinecraftVersion.equalsIgnoreCase(minecraftVersion)) {
project.getLogger().warn("Minecraft Version ({}) does not match yarn's minecraft version ({})", minecraftVersion, yarnMinecraftVersion);
}
// We can save reading the zip file + header by checking the file name
return mappingsJar.getName().endsWith("-v2.jar");
} else {
return doesJarContainV2Mappings(mappingsJar.toPath());
}
}
private void storeMappings(Project project, MinecraftProvider minecraftProvider, Path yarnJar) throws IOException {
project.getLogger().info(":extracting " + yarnJar.getFileName());
try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) {
extractMappings(fileSystem, baseTinyMappings); extractMappings(fileSystem, baseTinyMappings);
extractExtras(fileSystem); extractExtras(fileSystem);
} }
if (areMappingsV2(baseTinyMappings)) { if (areMappingsV2(baseTinyMappings)) {
// These are unmerged v2 mappings // These are unmerged v2 mappings
mergeAndSaveMappings(project, baseTinyMappings, tinyMappings); MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediaryService.get());
} else { } else {
if (minecraftProvider instanceof MergedMinecraftProvider mergedMinecraftProvider) { if (minecraftProvider instanceof MergedMinecraftProvider mergedMinecraftProvider) {
// These are merged v1 mappings // These are merged v1 mappings
Files.deleteIfExists(tinyMappings); Files.deleteIfExists(tinyMappings);
project.getLogger().lifecycle(":populating field names"); LOGGER.info(":populating field names");
suggestFieldNames(mergedMinecraftProvider, baseTinyMappings, tinyMappings); suggestFieldNames(mergedMinecraftProvider, baseTinyMappings, tinyMappings);
} else { } else {
throw new UnsupportedOperationException("V1 mappings only support merged minecraft"); throw new UnsupportedOperationException("V1 mappings only support merged minecraft");
@ -196,10 +204,14 @@ public class MappingsProviderImpl implements MappingsProvider {
} }
} }
private MemoryMappingTree readMappings() throws IOException { private MemoryMappingTree readMappings() {
try {
MemoryMappingTree mappingTree = new MemoryMappingTree(); MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingReader.read(tinyMappings, mappingTree); MappingReader.read(tinyMappings, mappingTree);
return mappingTree; return mappingTree;
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings", e);
}
} }
private static boolean areMappingsV2(Path path) throws IOException { private static boolean areMappingsV2(Path path) throws IOException {
@ -208,15 +220,7 @@ public class MappingsProviderImpl implements MappingsProvider {
} }
} }
private static boolean doesJarContainV2Mappings(Path path) throws IOException { public static void extractMappings(Path jar, Path extractTo) throws IOException {
try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) {
try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2;
}
}
}
private static void extractMappings(Path jar, Path extractTo) throws IOException {
try (FileSystem unmergedIntermediaryFs = FileSystems.newFileSystem(jar, (ClassLoader) null)) { try (FileSystem unmergedIntermediaryFs = FileSystems.newFileSystem(jar, (ClassLoader) null)) {
extractMappings(unmergedIntermediaryFs, extractTo); extractMappings(unmergedIntermediaryFs, extractTo);
} }
@ -271,7 +275,7 @@ public class MappingsProviderImpl implements MappingsProvider {
); );
} }
private void populateUnpickClasspath() { private void populateUnpickClasspath(Project project) {
String unpickCliName = "unpick-cli"; String unpickCliName = "unpick-cli";
project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH, project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH,
String.format("%s:%s:%s", unpickMetadata.unpickGroup, unpickCliName, unpickMetadata.unpickVersion) String.format("%s:%s:%s", unpickMetadata.unpickGroup, unpickCliName, unpickMetadata.unpickVersion)
@ -292,76 +296,6 @@ public class MappingsProviderImpl implements MappingsProvider {
} }
} }
private void mergeAndSaveMappings(Project project, Path from, Path out) throws IOException {
Stopwatch stopwatch = Stopwatch.createStarted();
project.getLogger().info(":merging mappings");
MemoryMappingTree intermediaryTree = new MemoryMappingTree();
readIntermediaryTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString()));
try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, intermediaryTree);
}
MemoryMappingTree officialTree = new MemoryMappingTree();
MappingNsCompleter nsCompleter = new MappingNsCompleter(officialTree, Map.of(MappingsNamespace.OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString()));
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nsCompleter, MappingsNamespace.OFFICIAL.toString());
intermediaryTree.accept(nsSwitch);
inheritMappedNamesOfEnclosingClasses(officialTree);
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) {
officialTree.accept(writer);
}
project.getLogger().info(":merged mappings in " + stopwatch.stop());
}
/**
* Searches the mapping tree for inner classes with no mapped name, whose enclosing classes have mapped names.
* Currently, Yarn does not export mappings for these inner classes.
*/
private void inheritMappedNamesOfEnclosingClasses(MemoryMappingTree tree) {
int intermediaryIdx = tree.getNamespaceId("intermediary");
int namedIdx = tree.getNamespaceId("named");
// The tree does not have an index by intermediary names by default
tree.setIndexByDstNames(true);
for (MappingTree.ClassMapping classEntry : tree.getClasses()) {
String intermediaryName = classEntry.getDstName(intermediaryIdx);
String namedName = classEntry.getDstName(namedIdx);
if (intermediaryName.equals(namedName) && intermediaryName.contains("$")) {
String[] path = intermediaryName.split(Pattern.quote("$"));
int parts = path.length;
for (int i = parts - 2; i >= 0; i--) {
String currentPath = String.join("$", Arrays.copyOfRange(path, 0, i + 1));
String namedParentClass = tree.mapClassName(currentPath, intermediaryIdx, namedIdx);
if (!namedParentClass.equals(currentPath)) {
classEntry.setDstName(namedParentClass
+ "$" + String.join("$", Arrays.copyOfRange(path, i + 1, path.length)),
namedIdx);
break;
}
}
}
}
}
private MemoryMappingTree readIntermediaryTree() throws IOException {
MemoryMappingTree tree = new MemoryMappingTree();
MappingNsCompleter nsCompleter = new MappingNsCompleter(tree, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true);
try (BufferedReader reader = Files.newBufferedReader(getIntermediaryTiny(), StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, nsCompleter);
}
return tree;
}
private void suggestFieldNames(MergedMinecraftProvider minecraftProvider, Path oldMappings, Path newMappings) { private void suggestFieldNames(MergedMinecraftProvider minecraftProvider, Path oldMappings, Path newMappings) {
Command command = new CommandProposeFieldNames(); Command command = new CommandProposeFieldNames();
runCommand(command, minecraftProvider.getMergedJar().getAbsolutePath(), runCommand(command, minecraftProvider.getMergedJar().getAbsolutePath(),
@ -377,19 +311,7 @@ public class MappingsProviderImpl implements MappingsProvider {
} }
} }
private void initFiles() { private static void cleanWorkingDirectory(Path mappingsWorkingDir) {
mappingsWorkingDir = minecraftProvider.dir(mappingsIdentifier).toPath();
baseTinyMappings = mappingsWorkingDir.resolve("mappings-base.tiny");
tinyMappings = mappingsWorkingDir.resolve("mappings.tiny");
tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar");
unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick");
if (isRefreshDeps()) {
cleanFiles();
}
}
public void cleanFiles() {
try { try {
if (Files.exists(mappingsWorkingDir)) { if (Files.exists(mappingsWorkingDir)) {
Files.walkFileTree(mappingsWorkingDir, new DeletingFileVisitor()); Files.walkFileTree(mappingsWorkingDir, new DeletingFileVisitor());
@ -401,34 +323,20 @@ public class MappingsProviderImpl implements MappingsProvider {
} }
} }
public Path getIntermediaryTiny() throws IOException {
if (intermediaryTiny == null) {
intermediaryTiny = minecraftProvider.file("intermediary-v2.tiny").toPath();
if (!Files.exists(intermediaryTiny) || (isRefreshDeps() && !hasRefreshed)) {
hasRefreshed = true;
// Download and extract intermediary
String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(minecraftProvider.minecraftVersion());
String intermediaryArtifactUrl = extension.getIntermediaryUrl(encodedMinecraftVersion);
File intermediaryJar = minecraftProvider.file("intermediary-v2.jar");
DownloadUtil.downloadIfChanged(new URL(intermediaryArtifactUrl), intermediaryJar, project.getLogger());
extractMappings(intermediaryJar.toPath(), intermediaryTiny);
}
}
return intermediaryTiny;
}
@Override @Override
public Path mappingsWorkingDir() { public Path mappingsWorkingDir() {
return mappingsWorkingDir; return mappingsWorkingDir;
} }
private String createMappingsIdentifier(String mappingsName, String version, String classifier) { @Override
public File intermediaryTinyFile() {
return intermediaryService.get().getIntermediaryTiny().toFile();
}
private static String createMappingsIdentifier(String mappingsName, String version, String classifier, String minecraftVersion) {
// mappingsName . mcVersion . version classifier // mappingsName . mcVersion . version classifier
// Example: net.fabricmc.yarn . 1_16_5 . 1.16.5+build.5 -v2 // Example: net.fabricmc.yarn . 1_16_5 . 1.16.5+build.5 -v2
return mappingsName + "." + minecraftProvider.minecraftVersion().replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier; return mappingsName + "." + minecraftVersion.replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier;
} }
public String mappingsIdentifier() { public String mappingsIdentifier() {
@ -448,15 +356,6 @@ public class MappingsProviderImpl implements MappingsProvider {
return signatureFixes; return signatureFixes;
} }
@Override
public File intermediaryTinyFile() {
try {
return getIntermediaryTiny().toFile();
} catch (IOException e) {
throw new RuntimeException("Failed to get intermediary", e);
}
}
public String getBuildServiceName(String name, String from, String to) { public String getBuildServiceName(String name, String from, String to) {
return "%s:%s:%s>%S".formatted(name, mappingsIdentifier(), from, to); return "%s:%s:%s>%S".formatted(name, mappingsIdentifier(), from, to);
} }
@ -464,7 +363,12 @@ public class MappingsProviderImpl implements MappingsProvider {
public record UnpickMetadata(String unpickGroup, String unpickVersion) { public record UnpickMetadata(String unpickGroup, String unpickVersion) {
} }
protected boolean isRefreshDeps() { protected static boolean isRefreshDeps() {
return LoomGradlePlugin.refreshDeps; return LoomGradlePlugin.refreshDeps;
} }
@Override
public void close() throws IOException {
mappingTree = null;
}
} }

View File

@ -24,20 +24,17 @@
package net.fabricmc.loom.configuration.providers.mappings.intermediary; package net.fabricmc.loom.configuration.providers.mappings.intermediary;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections; import java.util.Collections;
import java.util.function.Supplier;
import net.fabricmc.loom.api.mappings.layered.MappingLayer; import net.fabricmc.loom.api.mappings.layered.MappingLayer;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.MappingVisitor; import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.format.Tiny2Reader; import net.fabricmc.mappingio.tree.MemoryMappingTree;
public record IntermediaryMappingLayer(File tinyFile) implements MappingLayer { public record IntermediaryMappingLayer(Supplier<MemoryMappingTree> memoryMappingTree) implements MappingLayer {
@Override @Override
public MappingsNamespace getSourceNamespace() { public MappingsNamespace getSourceNamespace() {
return MappingsNamespace.OFFICIAL; return MappingsNamespace.OFFICIAL;
@ -48,8 +45,6 @@ public record IntermediaryMappingLayer(File tinyFile) implements MappingLayer {
// Populate named with intermediary and add Add a "named" namespace // Populate named with intermediary and add Add a "named" namespace
MappingNsCompleter nsCompleter = new MappingNsCompleter(mappingVisitor, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true); MappingNsCompleter nsCompleter = new MappingNsCompleter(mappingVisitor, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true);
try (BufferedReader reader = Files.newBufferedReader(tinyFile().toPath(), StandardCharsets.UTF_8)) { memoryMappingTree.get().accept(nsCompleter);
Tiny2Reader.read(reader, nsCompleter);
}
} }
} }

View File

@ -30,6 +30,6 @@ import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec;
public record IntermediaryMappingsSpec() implements MappingsSpec<IntermediaryMappingLayer> { public record IntermediaryMappingsSpec() implements MappingsSpec<IntermediaryMappingLayer> {
@Override @Override
public IntermediaryMappingLayer createLayer(MappingContext context) { public IntermediaryMappingLayer createLayer(MappingContext context) {
return new IntermediaryMappingLayer(context.mappingsProvider().intermediaryTinyFile()); return new IntermediaryMappingLayer(context.intermediaryTree());
} }
} }

View File

@ -0,0 +1,110 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.mappings.tiny;
import java.io.BufferedReader;
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.Map;
import java.util.regex.Pattern;
import com.google.common.base.Stopwatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryService;
import net.fabricmc.mappingio.adapter.MappingNsCompleter;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
import net.fabricmc.mappingio.format.Tiny2Reader;
import net.fabricmc.mappingio.format.Tiny2Writer;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public final class MappingsMerger {
private static final Logger LOGGER = LoggerFactory.getLogger(MappingsMerger.class);
public static void mergeAndSaveMappings(Path from, Path out, IntermediaryService intermediaryService) throws IOException {
Stopwatch stopwatch = Stopwatch.createStarted();
LOGGER.info(":merging mappings");
MemoryMappingTree intermediaryTree = new MemoryMappingTree();
intermediaryService.getMemoryMappingTree().accept(new MappingSourceNsSwitch(intermediaryTree, MappingsNamespace.INTERMEDIARY.toString()));
try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) {
Tiny2Reader.read(reader, intermediaryTree);
}
MemoryMappingTree officialTree = new MemoryMappingTree();
MappingNsCompleter nsCompleter = new MappingNsCompleter(officialTree, Map.of(MappingsNamespace.OFFICIAL.toString(), MappingsNamespace.INTERMEDIARY.toString()));
MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nsCompleter, MappingsNamespace.OFFICIAL.toString());
intermediaryTree.accept(nsSwitch);
inheritMappedNamesOfEnclosingClasses(officialTree);
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) {
officialTree.accept(writer);
}
LOGGER.info(":merged mappings in " + stopwatch.stop());
}
/**
* Searches the mapping tree for inner classes with no mapped name, whose enclosing classes have mapped names.
* Currently, Yarn does not export mappings for these inner classes.
*/
private static void inheritMappedNamesOfEnclosingClasses(MemoryMappingTree tree) {
int intermediaryIdx = tree.getNamespaceId("intermediary");
int namedIdx = tree.getNamespaceId("named");
// The tree does not have an index by intermediary names by default
tree.setIndexByDstNames(true);
for (MappingTree.ClassMapping classEntry : tree.getClasses()) {
String intermediaryName = classEntry.getDstName(intermediaryIdx);
String namedName = classEntry.getDstName(namedIdx);
if (intermediaryName.equals(namedName) && intermediaryName.contains("$")) {
String[] path = intermediaryName.split(Pattern.quote("$"));
int parts = path.length;
for (int i = parts - 2; i >= 0; i--) {
String currentPath = String.join("$", Arrays.copyOfRange(path, 0, i + 1));
String namedParentClass = tree.mapClassName(currentPath, intermediaryIdx, namedIdx);
if (!namedParentClass.equals(currentPath)) {
classEntry.setDstName(namedParentClass
+ "$" + String.join("$", Arrays.copyOfRange(path, i + 1, path.length)),
namedIdx);
break;
}
}
}
}
}
}

View File

@ -0,0 +1,55 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.mappings.tiny;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.format.MappingFormat;
public record TinyJarInfo(boolean v2, Optional<String> minecraftVersionId) {
public static TinyJarInfo get(Path jar) {
try {
return new TinyJarInfo(doesJarContainV2Mappings(jar), Optional.empty());
} catch (IOException e) {
throw new UncheckedIOException("Failed to read tiny jar info", e);
}
}
private static boolean doesJarContainV2Mappings(Path path) throws IOException {
try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) {
try (BufferedReader reader = Files.newBufferedReader(fs.getPath("mappings", "mappings.tiny"))) {
return MappingReader.detectFormat(reader) == MappingFormat.TINY_2;
}
}
}
}

View File

@ -41,6 +41,6 @@ public final class DecompilerConfiguration {
} }
private static void registerDecompiler(Project project, String name, Class<? extends LoomDecompiler> decompilerClass) { private static void registerDecompiler(Project project, String name, Class<? extends LoomDecompiler> decompilerClass) {
LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> options.getDecompilerClassname().set(decompilerClass.getName())); LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> options.getDecompilerClassName().set(decompilerClass.getName()));
} }
} }

View File

@ -24,7 +24,6 @@
package net.fabricmc.loom.extension; package net.fabricmc.loom.extension;
import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -41,7 +40,6 @@ import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
@ -60,7 +58,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
private final LoomFiles loomFiles; private final LoomFiles loomFiles;
private final ConfigurableFileCollection unmappedMods; private final ConfigurableFileCollection unmappedMods;
private final ConfigurableFileCollection mixinMappings;
private final MappingSet[] srcMappingCache = new MappingSet[2]; private final MappingSet[] srcMappingCache = new MappingSet[2];
private final Mercury[] srcMercuryCache = new Mercury[2]; private final Mercury[] srcMercuryCache = new Mercury[2];
private final Map<String, NamedDomainObjectProvider<Configuration>> lazyConfigurations = new HashMap<>(); private final Map<String, NamedDomainObjectProvider<Configuration>> lazyConfigurations = new HashMap<>();
@ -79,7 +76,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
this.project = project; this.project = project;
// Initiate with newInstance to allow gradle to decorate our extension // Initiate with newInstance to allow gradle to decorate our extension
this.mixinApExtension = project.getObjects().newInstance(MixinExtensionImpl.class, project); this.mixinApExtension = project.getObjects().newInstance(MixinExtensionImpl.class, project);
this.mixinMappings = project.getObjects().fileCollection();
this.loomFiles = files; this.loomFiles = files;
this.unmappedMods = project.files(); this.unmappedMods = project.files();
} }
@ -94,18 +90,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen
return loomFiles; return loomFiles;
} }
@Override
public synchronized File getMixinMappings(SourceSet sourceSet) {
File mixinMapping = new File(getFiles().getProjectBuildCache(), "mixin-map-" + getMappingsProvider().mappingsIdentifier() + "." + sourceSet.getName() + ".tiny");
mixinMappings.from(getProject().files(mixinMapping));
return mixinMapping;
}
@Override
public FileCollection getAllMixinMappings() {
return mixinMappings.filter(File::exists);
}
@Override @Override
public void setDependencyManager(LoomDependencyManager dependencyManager) { public void setDependencyManager(LoomDependencyManager dependencyManager) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;

View File

@ -0,0 +1,108 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.task;
import java.nio.file.Path;
import javax.inject.Inject;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;
import net.fabricmc.loom.task.service.TinyRemapperService;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
import net.fabricmc.tinyremapper.TinyRemapper;
/**
* The prepare remap task runs before all other jar remap tasks, should be used to setup tiny remapper.
*/
public abstract class PrepareJarRemapTask extends AbstractLoomTask {
private final RemapJarTask remapJarTask;
@InputFile
public abstract RegularFileProperty getInputFile();
@Inject
public PrepareJarRemapTask(RemapJarTask remapJarTask) {
this.remapJarTask = remapJarTask;
getInputFile().set(remapJarTask.getInputFile());
// TODO can this be up-to-date when the main task is up-to date?
getOutputs().upToDateWhen((o) -> false);
getProject().getGradle().allprojects(project -> {
project.getTasks().configureEach(task -> {
if (task instanceof PrepareJarRemapTask otherTask) {
if (otherTask == this) return;
// Ensure that all other prepare tasks inputs have completed
dependsOn(otherTask.getInputs());
mustRunAfter(otherTask.getInputs());
}
});
});
}
@Inject
protected abstract WorkerExecutor getWorkerExecutor();
@TaskAction
public void run() {
final WorkQueue workQueue = getWorkerExecutor().noIsolation();
workQueue.submit(ReadInputsAction.class, params -> {
params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), remapJarTask.getTinyRemapperService()));
params.getInputTagName().set(remapJarTask.getInputTagName());
params.getInputFile().set(getInputFile());
});
}
public interface ReadInputsParams extends WorkParameters {
Property<String> getTinyRemapperBuildServiceUuid();
Property<String> getInputTagName();
RegularFileProperty getInputFile();
}
public abstract static class ReadInputsAction implements WorkAction<ReadInputsParams> {
private final TinyRemapperService tinyRemapperService;
public ReadInputsAction() {
this.tinyRemapperService = UnsafeWorkQueueHelper.get(getParameters().getTinyRemapperBuildServiceUuid(), TinyRemapperService.class);
}
@Override
public void execute() {
final TinyRemapper tinyRemapper = tinyRemapperService.getTinyRemapperForInputs();
final Path inputFile = getParameters().getInputFile().getAsFile().get().toPath();
tinyRemapper.readInputsAsync(tinyRemapperService.createTag(getParameters().getInputTagName().get()), inputFile);
}
}
}

View File

@ -26,7 +26,6 @@ package net.fabricmc.loom.task;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.nio.file.Files; import java.nio.file.Files;
@ -34,12 +33,14 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.ConfigurableFileCollection;
@ -47,12 +48,11 @@ import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskAction;
import org.objectweb.asm.commons.Remapper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -60,20 +60,18 @@ import net.fabricmc.accesswidener.AccessWidenerReader;
import net.fabricmc.accesswidener.AccessWidenerRemapper; import net.fabricmc.accesswidener.AccessWidenerRemapper;
import net.fabricmc.accesswidener.AccessWidenerWriter; import net.fabricmc.accesswidener.AccessWidenerWriter;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.build.MixinRefmapHelper; import net.fabricmc.loom.build.MixinRefmapHelper;
import net.fabricmc.loom.build.nesting.IncludedJarFactory; import net.fabricmc.loom.build.nesting.IncludedJarFactory;
import net.fabricmc.loom.build.nesting.JarNester; import net.fabricmc.loom.build.nesting.JarNester;
import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile;
import net.fabricmc.loom.extension.MixinExtension; import net.fabricmc.loom.extension.MixinExtension;
import net.fabricmc.loom.task.service.JarManifestService; import net.fabricmc.loom.task.service.JarManifestService;
import net.fabricmc.loom.task.service.MappingsService; import net.fabricmc.loom.task.service.TinyRemapperService;
import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.tinyremapper.InputTag; import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper; import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.TinyUtils;
public abstract class RemapJarTask extends AbstractRemapJarTask { public abstract class RemapJarTask extends AbstractRemapJarTask {
private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
@ -84,6 +82,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
@Input @Input
public abstract Property<Boolean> getAddNestedDependencies(); public abstract Property<Boolean> getAddNestedDependencies();
private Supplier<TinyRemapperService> tinyRemapperService = Suppliers.memoize(() -> TinyRemapperService.getOrCreate(this));
@Inject @Inject
public RemapJarTask() { public RemapJarTask() {
super(); super();
@ -93,6 +93,25 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE); Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE);
getNestedJars().from(new IncludedJarFactory(getProject()).getNestedJars(includeConfiguration)); getNestedJars().from(new IncludedJarFactory(getProject()).getNestedJars(includeConfiguration));
setupPreparationTask();
}
private void setupPreparationTask() {
PrepareJarRemapTask prepareJarTask = getProject().getTasks().create("prepare" + getName().substring(0, 1).toUpperCase() + getName().substring(1), PrepareJarRemapTask.class, this);
dependsOn(prepareJarTask);
mustRunAfter(prepareJarTask);
getProject().getGradle().allprojects(project -> {
project.getTasks().configureEach(task -> {
if (task instanceof PrepareJarRemapTask otherTask) {
// Ensure that all remap jars run after all prepare tasks
dependsOn(otherTask);
mustRunAfter(otherTask);
}
});
});
} }
@TaskAction @TaskAction
@ -105,14 +124,14 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
} }
params.getJarManifestService().set(JarManifestService.get(getProject())); params.getJarManifestService().set(JarManifestService.get(getProject()));
params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), tinyRemapperService.get()));
params.getRemapClasspath().from(getClasspath()); params.getRemapClasspath().from(getClasspath());
params.getMappings().add(MappingsService.createDefault(getProject(), getSourceNamespace().get(), getTargetNamespace().get())); params.getInputTagName().set(getInputTagName());
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get(); final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
params.getUseMixinExtension().set(!legacyMixin); params.getUseMixinExtension().set(!legacyMixin);
if (legacyMixin) { if (legacyMixin) {
params.getMixinMappings().from(extension.getAllMixinMappings());
setupLegacyMixinRefmapRemapping(params); setupLegacyMixinRefmapRemapping(params);
} }
}); });
@ -174,8 +193,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
public interface RemapParams extends AbstractRemapParams { public interface RemapParams extends AbstractRemapParams {
ConfigurableFileCollection getNestedJars(); ConfigurableFileCollection getNestedJars();
ConfigurableFileCollection getRemapClasspath(); ConfigurableFileCollection getRemapClasspath();
ConfigurableFileCollection getMixinMappings(); Property<String> getInputTagName();
ListProperty<Provider<MappingsService>> getMappings();
Property<Boolean> getUseMixinExtension(); Property<Boolean> getUseMixinExtension();
@ -183,19 +201,25 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
ListProperty<RefmapData> getMixinData(); ListProperty<RefmapData> getMixinData();
Property<JarManifestService> getJarManifestService(); Property<JarManifestService> getJarManifestService();
Property<String> getTinyRemapperBuildServiceUuid();
} }
public abstract static class RemapAction extends AbstractRemapAction<RemapParams> { public abstract static class RemapAction extends AbstractRemapAction<RemapParams> {
private static final Logger LOGGER = LoggerFactory.getLogger(RemapAction.class); private static final Logger LOGGER = LoggerFactory.getLogger(RemapAction.class);
private final TinyRemapperService tinyRemapperService;
private TinyRemapper tinyRemapper; private TinyRemapper tinyRemapper;
public RemapAction() {
this.tinyRemapperService = UnsafeWorkQueueHelper.get(getParameters().getTinyRemapperBuildServiceUuid(), TinyRemapperService.class);
}
@Override @Override
public void execute() { public void execute() {
try { try {
LOGGER.info("Remapping {} to {}", inputFile, outputFile); LOGGER.info("Remapping {} to {}", inputFile, outputFile);
tinyRemapper = createTinyRemapper(); tinyRemapper = tinyRemapperService.getTinyRemapperForRemapping();
remap(); remap();
remapAccessWidener(); remapAccessWidener();
@ -204,9 +228,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
modifyJarManifest(); modifyJarManifest();
rewriteJar(); rewriteJar();
tinyRemapper.finish();
tinyRemapper = null;
LOGGER.debug("Finished remapping {}", inputFile); LOGGER.debug("Finished remapping {}", inputFile);
} catch (Exception e) { } catch (Exception e) {
try { try {
@ -220,13 +241,9 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
} }
private void remap() throws IOException { private void remap() throws IOException {
final InputTag inputTag = tinyRemapper.createInputTag();
tinyRemapper.readInputsAsync(inputTag, inputFile);
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(outputFile).build()) { try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(outputFile).build()) {
outputConsumer.addNonClassFiles(inputFile); outputConsumer.addNonClassFiles(inputFile);
tinyRemapper.apply(outputConsumer, inputTag); tinyRemapper.apply(outputConsumer, tinyRemapperService.getTag(getParameters().getInputTagName().get()));
} }
} }
@ -237,21 +254,21 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
return; return;
} }
byte[] remapped = remapAccessWidener(accessWidenerFile.content(), tinyRemapper.getEnvironment().getRemapper(), MappingsNamespace.INTERMEDIARY.toString()); byte[] remapped = remapAccessWidener(accessWidenerFile.content());
// Finally, replace the output with the remaped aw // Finally, replace the output with the remaped aw
ZipUtils.replace(outputFile, accessWidenerFile.path(), remapped); ZipUtils.replace(outputFile, accessWidenerFile.path(), remapped);
} }
private static byte[] remapAccessWidener(byte[] input, Remapper asmRemapper, String targetNamespace) { private byte[] remapAccessWidener(byte[] input) {
int version = AccessWidenerReader.readVersion(input); int version = AccessWidenerReader.readVersion(input);
AccessWidenerWriter writer = new AccessWidenerWriter(version); AccessWidenerWriter writer = new AccessWidenerWriter(version);
AccessWidenerRemapper remapper = new AccessWidenerRemapper( AccessWidenerRemapper remapper = new AccessWidenerRemapper(
writer, writer,
asmRemapper, tinyRemapper.getEnvironment().getRemapper(),
MappingsNamespace.NAMED.toString(), getParameters().getSourceNamespace().get(),
targetNamespace getParameters().getTargetNamespace().get()
); );
AccessWidenerReader reader = new AccessWidenerReader(remapper); AccessWidenerReader reader = new AccessWidenerReader(remapper);
reader.read(input); reader.read(input);
@ -300,30 +317,15 @@ public abstract class RemapJarTask extends AbstractRemapJarTask {
}))); })));
} }
} }
private TinyRemapper createTinyRemapper() {
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
for (Provider<MappingsService> provider : getParameters().getMappings().get()) {
builder.withMappings(provider.get().getMappingsProvider());
} }
for (File mixinMapping : getParameters().getMixinMappings()) { @Internal
builder.withMappings(TinyUtils.createTinyMappingProvider(mixinMapping.toPath(), getParameters().getSourceNamespace().get(), getParameters().getTargetNamespace().get())); public TinyRemapperService getTinyRemapperService() {
return tinyRemapperService.get();
} }
if (getParameters().getUseMixinExtension().get()) { @Internal
builder.extension(new net.fabricmc.tinyremapper.extension.mixin.MixinExtension()); String getInputTagName() {
} return getProject().getPath() + getName();
TinyRemapper remapper = builder.build();
// Apply classpath
for (File file : getParameters().getRemapClasspath()) {
remapper.readClassPathAsync(file.toPath());
}
return remapper;
}
} }
} }

View File

@ -35,8 +35,8 @@ import org.gradle.api.tasks.TaskAction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import net.fabricmc.loom.task.service.MappingsService;
import net.fabricmc.loom.task.service.SourceRemapperService; import net.fabricmc.loom.task.service.SourceRemapperService;
import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper;
public abstract class RemapSourcesJarTask extends AbstractRemapJarTask { public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
@Inject @Inject
@ -49,12 +49,12 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
@TaskAction @TaskAction
public void run() { public void run() {
submitWork(RemapSourcesAction.class, params -> { submitWork(RemapSourcesAction.class, params -> {
params.getSourcesRemapperService().set(SourceRemapperService.create(getProject(), MappingsService.createDefault(getProject(), getSourceNamespace().get(), getTargetNamespace().get()), getClasspath())); params.getSourcesRemapperServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), SourceRemapperService.create(this)));
}); });
} }
public interface RemapSourcesParams extends AbstractRemapParams { public interface RemapSourcesParams extends AbstractRemapParams {
Property<SourceRemapperService> getSourcesRemapperService(); Property<String> getSourcesRemapperServiceUuid();
} }
public abstract static class RemapSourcesAction extends AbstractRemapAction<RemapSourcesParams> { public abstract static class RemapSourcesAction extends AbstractRemapAction<RemapSourcesParams> {
@ -65,7 +65,7 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask {
public RemapSourcesAction() { public RemapSourcesAction() {
super(); super();
sourceRemapperService = getParameters().getSourcesRemapperService().get(); sourceRemapperService = UnsafeWorkQueueHelper.get(getParameters().getSourcesRemapperServiceUuid(), SourceRemapperService.class);
} }
@Override @Override

View File

@ -54,8 +54,8 @@ public class RemapTaskConfiguration {
return; return;
} }
// Register the default remap jar task // Register the default remap jar task - must not be lazy to ensure that the prepare tasks get setup for other projects to depend on.
TaskProvider<RemapJarTask> remapJarTaskProvider = tasks.register(REMAP_JAR_TASK_NAME, RemapJarTask.class, task -> { RemapJarTask remapJarTask = tasks.create(REMAP_JAR_TASK_NAME, RemapJarTask.class, task -> {
final AbstractArchiveTask jarTask = tasks.named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).get(); final AbstractArchiveTask jarTask = tasks.named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).get();
// Basic task setup // Basic task setup
@ -76,7 +76,7 @@ public class RemapTaskConfiguration {
task.getDestinationDirectory().set(new File(project.getBuildDir(), "devlibs")); task.getDestinationDirectory().set(new File(project.getBuildDir(), "devlibs"));
}); });
tasks.named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(remapJarTaskProvider)); tasks.named(BasePlugin.ASSEMBLE_TASK_NAME).configure(task -> task.dependsOn(remapJarTask));
trySetupSourceRemapping(project); trySetupSourceRemapping(project);

View File

@ -24,49 +24,45 @@
package net.fabricmc.loom.task.service; package net.fabricmc.loom.task.service;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.nio.file.Path;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.TinyRemapperHelper;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree;
import net.fabricmc.tinyremapper.IMappingProvider; import net.fabricmc.tinyremapper.IMappingProvider;
public abstract class MappingsService implements BuildService<MappingsService.Params>, AutoCloseable { public final class MappingsService implements SharedService {
interface Params extends BuildServiceParameters { private record Options(Path mappingsFile, String from, String to, boolean remapLocals) { }
RegularFileProperty getMappingsFile();
Property<String> getFromNamespace(); public static MappingsService create(Project project, String name, Path mappingsFile, String from, String to, boolean remapLocals) {
Property<String> getToNamespace(); return create(SharedServiceManager.get(project), name, mappingsFile, from, to, remapLocals);
Property<Boolean> getRemapLocals();
} }
public static synchronized Provider<MappingsService> create(Project project, String name, File mappingsFile, String from, String to, boolean remapLocals) { public static synchronized MappingsService create(SharedServiceManager sharedServiceManager, String name, Path mappingsFile, String from, String to, boolean remapLocals) {
return project.getGradle().getSharedServices().registerIfAbsent(name, MappingsService.class, spec -> { final Options options = new Options(mappingsFile, from, to, remapLocals);
spec.parameters(params -> { final String id = name + options.hashCode();
params.getMappingsFile().set(mappingsFile); return sharedServiceManager.getOrCreateService(id, () -> new MappingsService(options));
params.getFromNamespace().set(from);
params.getToNamespace().set(to);
params.getRemapLocals().set(remapLocals);
});
});
} }
public static Provider<MappingsService> createDefault(Project project, String from, String to) { public static MappingsService createDefault(Project project, String from, String to) {
final MappingsProviderImpl mappingsProvider = LoomGradleExtension.get(project).getMappingsProvider(); final MappingsProviderImpl mappingsProvider = LoomGradleExtension.get(project).getMappingsProvider();
final String name = mappingsProvider.getBuildServiceName("mappingsProvider", from, to); final String name = mappingsProvider.getBuildServiceName("mappingsProvider", from, to);
return MappingsService.create(project, name, mappingsProvider.tinyMappings.toFile(), from, to, false); return MappingsService.create(project, name, mappingsProvider.tinyMappings, from, to, false);
}
private final Options options;
public MappingsService(Options options) {
this.options = options;
} }
private IMappingProvider mappingProvider = null; private IMappingProvider mappingProvider = null;
@ -76,13 +72,13 @@ public abstract class MappingsService implements BuildService<MappingsService.Pa
if (mappingProvider == null) { if (mappingProvider == null) {
try { try {
mappingProvider = TinyRemapperHelper.create( mappingProvider = TinyRemapperHelper.create(
getParameters().getMappingsFile().get().getAsFile().toPath(), options.mappingsFile(),
getParameters().getFromNamespace().get(), options.from(),
getParameters().getToNamespace().get(), options.to(),
getParameters().getRemapLocals().get() options.remapLocals()
); );
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings from: " + getParameters().getMappingsFile().get().getAsFile().getAbsolutePath(), e); throw new UncheckedIOException("Failed to read mappings from: " + options.mappingsFile(), e);
} }
} }
@ -94,9 +90,9 @@ public abstract class MappingsService implements BuildService<MappingsService.Pa
memoryMappingTree = new MemoryMappingTree(); memoryMappingTree = new MemoryMappingTree();
try { try {
MappingReader.read(getParameters().getMappingsFile().get().getAsFile().toPath(), memoryMappingTree); MappingReader.read(options.mappingsFile(), memoryMappingTree);
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException("Failed to read mappings from: " + getParameters().getMappingsFile().get().getAsFile().getAbsolutePath(), e); throw new UncheckedIOException("Failed to read mappings from: " + options.mappingsFile(), e);
} }
} }
@ -104,11 +100,11 @@ public abstract class MappingsService implements BuildService<MappingsService.Pa
} }
public String getFromNamespace() { public String getFromNamespace() {
return getParameters().getFromNamespace().get(); return options.from();
} }
public String getToNamespace() { public String getToNamespace() {
return getParameters().getToNamespace().get(); return options.to();
} }
@Override @Override

View File

@ -0,0 +1,69 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.service;
import java.io.File;
import java.util.HashSet;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.tinyremapper.IMappingProvider;
public final class MixinMappingsService implements SharedService {
private final SharedServiceManager sharedServiceManager;
private final HashSet<File> mixinMappings = new HashSet<>();
private MixinMappingsService(SharedServiceManager sharedServiceManager) {
this.sharedServiceManager = sharedServiceManager;
}
public static File getMixinMappingFile(Project project, SourceSet sourceSet) {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
File mixinMapping = new File(extension.getFiles().getProjectBuildCache(), "mixin-map-" + extension.getMappingsProvider().mappingsIdentifier() + "." + sourceSet.getName() + ".tiny");
getService(SharedServiceManager.get(project)).mixinMappings.add(mixinMapping);
return mixinMapping;
}
static MixinMappingsService getService(SharedServiceManager sharedServiceManager) {
return sharedServiceManager.getOrCreateService("MixinMappings", () -> new MixinMappingsService(sharedServiceManager));
}
IMappingProvider getMappingProvider(String from, String to) {
return out -> {
for (File mixinMapping : mixinMappings) {
if (!mixinMapping.exists()) continue;
MappingsService service = MappingsService.create(sharedServiceManager, mixinMapping.getAbsolutePath(), mixinMapping.toPath(), from, to, false);
service.getMappingsProvider().load(out);
}
};
}
}

View File

@ -24,49 +24,57 @@
package net.fabricmc.loom.task.service; package net.fabricmc.loom.task.service;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import org.cadixdev.lorenz.MappingSet; import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.mercury.Mercury; import org.cadixdev.mercury.Mercury;
import org.cadixdev.mercury.remapper.MercuryRemapper; import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.task.RemapSourcesJarTask;
import net.fabricmc.loom.util.DeletingFileVisitor; import net.fabricmc.loom.util.DeletingFileVisitor;
import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.SourceRemapper; import net.fabricmc.loom.util.SourceRemapper;
import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.ZipUtils;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.lorenztiny.TinyMappingsReader; import net.fabricmc.lorenztiny.TinyMappingsReader;
public abstract class SourceRemapperService implements BuildService<SourceRemapperService.Params>, AutoCloseable { public final class SourceRemapperService implements SharedService {
public interface Params extends BuildServiceParameters { public static synchronized SourceRemapperService create(RemapSourcesJarTask task) {
Property<Provider<MappingsService>> getMappings(); final Project project = task.getProject();
final String to = task.getTargetNamespace().get();
final String from = task.getSourceNamespace().get();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
final String id = extension.getMappingsProvider().getBuildServiceName("sourceremapper", from, to);
ConfigurableFileCollection getClasspath(); return sharedServiceManager.getOrCreateService(id, () ->
} new SourceRemapperService(MappingsService.createDefault(project, from, to), task.getClasspath()
public static synchronized Provider<SourceRemapperService> create(Project project, Provider<MappingsService> mappings, FileCollection classpath) {
// TODO may need a better name, im not too sure
return project.getGradle().getSharedServices().registerIfAbsent("sourceremapper", SourceRemapperService.class, spec ->
spec.parameters(params -> {
params.getMappings().set(mappings);
params.getClasspath().from(classpath);
}
)); ));
} }
private static final Logger LOGGER = LoggerFactory.getLogger(SourceRemapperService.class); private static final Logger LOGGER = LoggerFactory.getLogger(SourceRemapperService.class);
private Mercury mercury; private final MappingsService mappingsService;
private final ConfigurableFileCollection classpath;
private final Supplier<Mercury> mercury = Suppliers.memoize(this::createMercury);
private SourceRemapperService(MappingsService mappingsService, ConfigurableFileCollection classpath) {
this.mappingsService = mappingsService;
this.classpath = classpath;
}
public void remapSourcesJar(Path source, Path destination) throws IOException { public void remapSourcesJar(Path source, Path destination) throws IOException {
if (source.equals(destination)) { if (source.equals(destination)) {
@ -99,35 +107,34 @@ public abstract class SourceRemapperService implements BuildService<SourceRemapp
} }
} }
private synchronized void doRemap(Path srcPath, Path dstPath, Path source) throws IOException { private synchronized void doRemap(Path srcPath, Path dstPath, Path source) {
if (mercury == null) {
mercury = new Mercury();
mercury.setGracefulClasspathChecks(true);
mercury.getProcessors().add(MercuryRemapper.create(getMappings()));
getParameters().getClasspath().forEach(file -> mercury.getClassPath().add(file.toPath()));
}
try { try {
// Not thread safe!! synchronized (mercury) {
mercury.rewrite(srcPath, dstPath); mercury.get().rewrite(srcPath, dstPath);
}
} catch (Exception e) { } catch (Exception e) {
LOGGER.warn("Could not remap " + source + " fully!", e); LOGGER.warn("Could not remap " + source + " fully!", e);
} }
} }
private MappingSet getMappings() throws IOException { private MappingSet getMappings() throws IOException {
return new TinyMappingsReader(mappingsService().getMemoryMappingTree(), mappingsService().getFromNamespace(), mappingsService().getToNamespace()).read(); return new TinyMappingsReader(mappingsService.getMemoryMappingTree(), mappingsService.getFromNamespace(), mappingsService.getToNamespace()).read();
} }
private MappingsService mappingsService() { private Mercury createMercury() {
return getParameters().getMappings().get().get(); var mercury = new Mercury();
mercury.setGracefulClasspathChecks(true);
try {
mercury.getProcessors().add(MercuryRemapper.create(getMappings()));
} catch (IOException e) {
throw new UncheckedIOException("Failed to read mercury mappings", e);
} }
@Override for (File file : classpath.getFiles()) {
public void close() throws Exception { mercury.getClassPath().add(file.toPath());
mercury = null; }
// This is required (:
System.gc(); return mercury;
} }
} }

View File

@ -0,0 +1,142 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.task.AbstractRemapJarTask;
import net.fabricmc.loom.util.service.SharedService;
import net.fabricmc.loom.util.service.SharedServiceManager;
import net.fabricmc.tinyremapper.IMappingProvider;
import net.fabricmc.tinyremapper.InputTag;
import net.fabricmc.tinyremapper.TinyRemapper;
public class TinyRemapperService implements SharedService {
public static synchronized TinyRemapperService getOrCreate(AbstractRemapJarTask remapJarTask) {
final Project project = remapJarTask.getProject();
final String to = remapJarTask.getTargetNamespace().get();
final String from = remapJarTask.getSourceNamespace().get();
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final SharedServiceManager sharedServiceManager = SharedServiceManager.get(project);
final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get();
// Generates an id that is used to share the remapper across projects. This tasks in the remap jar task name to handle custom remap jar tasks separately.
final String id = extension.getMappingsProvider().getBuildServiceName("remapJarService", from, to) + ":" + remapJarTask.getName();
TinyRemapperService service = sharedServiceManager.getOrCreateService(id, () -> {
List<IMappingProvider> mappings = new ArrayList<>();
mappings.add(MappingsService.createDefault(project, from, to).getMappingsProvider());
if (legacyMixin) {
mappings.add(MixinMappingsService.getService(SharedServiceManager.get(project)).getMappingProvider(from, to));
}
return new TinyRemapperService(mappings, !legacyMixin);
});
service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).toList());
return service;
}
private TinyRemapper tinyRemapper;
private final Map<String, InputTag> inputTagMap = new ConcurrentHashMap<>();
private final HashSet<Path> classpath = new HashSet<>();
// Set to true once remapping has started, once set no inputs can be read.
private boolean isRemapping = false;
public TinyRemapperService(List<IMappingProvider> mappings, boolean useMixinExtension) {
TinyRemapper.Builder builder = TinyRemapper.newRemapper();
for (IMappingProvider provider : mappings) {
builder.withMappings(provider);
}
if (useMixinExtension) {
builder.extension(new net.fabricmc.tinyremapper.extension.mixin.MixinExtension());
}
tinyRemapper = builder.build();
}
public InputTag createTag(String key) {
if (inputTagMap.containsKey(key)) {
throw new IllegalStateException("Input tag already exists for key: " + key);
}
return inputTagMap.put(key, tinyRemapper.createInputTag());
}
public InputTag getTag(String key) {
return Objects.requireNonNull(inputTagMap.get(key), "Input tag not found for: " + key);
}
public TinyRemapper getTinyRemapperForRemapping() {
synchronized (this) {
isRemapping = true;
return Objects.requireNonNull(tinyRemapper, "Tiny remapper has not been setup");
}
}
public synchronized TinyRemapper getTinyRemapperForInputs() {
synchronized (this) {
if (isRemapping) {
throw new IllegalStateException("Cannot read inputs as remapping has already started");
}
return tinyRemapper;
}
}
void readClasspath(List<Path> paths) {
List<Path> toRead;
synchronized (classpath) {
toRead = paths.stream().filter(path -> !classpath.contains(path)).toList();
classpath.addAll(paths);
}
tinyRemapper.readClassPathAsync(toRead.toArray(Path[]::new));
}
@Override
public void close() throws IOException {
if (tinyRemapper != null) {
tinyRemapper.finish();
tinyRemapper = null;
}
}
}

View File

@ -35,7 +35,7 @@ import java.util.zip.GZIPInputStream;
import com.google.common.io.Files; import com.google.common.io.Files;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.logging.Logger; import org.slf4j.Logger;
import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.LoomGradlePlugin;

View File

@ -110,21 +110,31 @@ public final class TinyRemapperHelper {
public static IMappingProvider create(MappingTree mappings, String from, String to, boolean remapLocalVariables) { public static IMappingProvider create(MappingTree mappings, String from, String to, boolean remapLocalVariables) {
return (acceptor) -> { return (acceptor) -> {
final int fromId = mappings.getNamespaceId(from);
final int toId = mappings.getNamespaceId(to);
for (MappingTree.ClassMapping classDef : mappings.getClasses()) { for (MappingTree.ClassMapping classDef : mappings.getClasses()) {
String className = classDef.getName(from); String className = classDef.getName(fromId);
acceptor.acceptClass(className, classDef.getName(to)); String dstName = classDef.getName(toId);
if (dstName == null) {
// Unsure if this is correct, should be better than crashing tho.
dstName = className;
}
acceptor.acceptClass(className, dstName);
for (MappingTree.FieldMapping field : classDef.getFields()) { for (MappingTree.FieldMapping field : classDef.getFields()) {
acceptor.acceptField(memberOf(className, field.getName(from), field.getDesc(from)), field.getName(to)); acceptor.acceptField(memberOf(className, field.getName(fromId), field.getDesc(fromId)), field.getName(toId));
} }
for (MappingTree.MethodMapping method : classDef.getMethods()) { for (MappingTree.MethodMapping method : classDef.getMethods()) {
IMappingProvider.Member methodIdentifier = memberOf(className, method.getName(from), method.getDesc(from)); IMappingProvider.Member methodIdentifier = memberOf(className, method.getName(fromId), method.getDesc(fromId));
acceptor.acceptMethod(methodIdentifier, method.getName(to)); acceptor.acceptMethod(methodIdentifier, method.getName(toId));
if (remapLocalVariables) { if (remapLocalVariables) {
for (MappingTree.MethodArgMapping parameter : method.getArgs()) { for (MappingTree.MethodArgMapping parameter : method.getArgs()) {
String name = parameter.getName(to); String name = parameter.getName(toId);
if (name == null) { if (name == null) {
continue; continue;
@ -136,7 +146,7 @@ public final class TinyRemapperHelper {
for (MappingTree.MethodVarMapping localVariable : method.getVars()) { for (MappingTree.MethodVarMapping localVariable : method.getVars()) {
acceptor.acceptMethodVar(methodIdentifier, localVariable.getLvIndex(), acceptor.acceptMethodVar(methodIdentifier, localVariable.getLvIndex(),
localVariable.getStartOpIdx(), localVariable.getLvtRowIndex(), localVariable.getStartOpIdx(), localVariable.getLvtRowIndex(),
localVariable.getName(to)); localVariable.getName(toId));
} }
} }
} }

View File

@ -0,0 +1,34 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.service;
import java.io.Closeable;
import java.io.IOException;
public interface SharedService extends Closeable {
@Override
default void close() throws IOException {
}
}

View File

@ -0,0 +1,110 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.gradle.BuildResult;
import org.gradle.api.Project;
import org.gradle.api.invocation.Gradle;
/**
* A simple manager for {@link SharedService} to be used across gradle (sub) projects.
* This is a basic replacement for gradle's build service api.
*/
public final class SharedServiceManager {
private static final Map<Gradle, SharedServiceManager> SERVICE_FACTORY_MAP = new ConcurrentHashMap<>();
private final Gradle gradle;
private final Map<String, SharedService> sharedServiceMap = new ConcurrentHashMap<>();
private boolean shutdown = false;
private SharedServiceManager(Gradle gradle) {
this.gradle = gradle;
this.gradle.buildFinished(this::onFinish);
}
public static SharedServiceManager get(Project project) {
return get(project.getGradle());
}
public static SharedServiceManager get(Gradle gradle) {
return SERVICE_FACTORY_MAP.computeIfAbsent(gradle, SharedServiceManager::new);
}
public <S extends SharedService> S getOrCreateService(String id, Supplier<S> function) {
synchronized (sharedServiceMap) {
if (shutdown) {
throw new UnsupportedOperationException("Cannot get or create service has the manager has been shutdown.");
}
//noinspection unchecked
S sharedService = (S) sharedServiceMap.get(id);
if (sharedService == null) {
sharedService = function.get();
sharedServiceMap.put(id, sharedService);
}
return sharedService;
}
}
private void onFinish(BuildResult buildResult) {
synchronized (sharedServiceMap) {
shutdown = true;
}
SERVICE_FACTORY_MAP.remove(gradle);
final List<IOException> exceptionList = new ArrayList<>();
for (SharedService sharedService : sharedServiceMap.values()) {
try {
sharedService.close();
} catch (IOException e) {
exceptionList.add(e);
}
}
sharedServiceMap.clear();
if (!exceptionList.isEmpty()) {
// Done to try and close all the services.
RuntimeException exception = new RuntimeException("Failed to close all shared services");
exceptionList.forEach(exception::addSuppressed);
throw exception;
}
// This is required to ensure that mercury releases all of the file handles.
System.gc();
}
}

View File

@ -0,0 +1,60 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.service;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
// Massive hack to work around WorkerExecutor.noIsolation() doing isolation checks.
public final class UnsafeWorkQueueHelper {
private static final Map<String, SharedService> SERVICE_MAP = new ConcurrentHashMap<>();
private UnsafeWorkQueueHelper() {
}
public static String create(Project project, SharedService service) {
final String uuid = UUID.randomUUID().toString();
SERVICE_MAP.put(uuid, service);
// Ensure we don't make a mess if things go wrong.
project.getGradle().buildFinished(buildResult -> SERVICE_MAP.remove(uuid));
return uuid;
}
public static <S> S get(Property<String> property, Class<S> clazz) {
SharedService service = SERVICE_MAP.remove(property.get());
if (service == null) {
throw new NullPointerException("Failed to get service for " + clazz);
}
//noinspection unchecked
return (S) service;
}
}

View File

@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion
class LoomTestConstants { class LoomTestConstants {
public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion() public final static String DEFAULT_GRADLE = GradleVersion.current().getVersion()
public final static String PRE_RELEASE_GRADLE = "7.5-20220101231120+0000" public final static String PRE_RELEASE_GRADLE = "7.5-20220110230252+0000"
public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE] public final static String[] STANDARD_TEST_VERSIONS = [DEFAULT_GRADLE, PRE_RELEASE_GRADLE]
} }

View File

@ -0,0 +1,63 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2022 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.test.benchmark
import groovy.time.TimeCategory
import groovy.time.TimeDuration
import net.fabricmc.loom.test.LoomTestConstants
import net.fabricmc.loom.test.util.GradleProjectTestTrait
/**
* Run this class, passing a working dir as the first argument.
* Allow for one warm up run before profiling, follow up runs should not be using the network.
*/
@Singleton
class FabricAPIBenchmark implements GradleProjectTestTrait {
def run(File dir) {
def gradle = gradleProject(
version: LoomTestConstants.PRE_RELEASE_GRADLE,
projectDir: new File(dir, "project"),
gradleHomeDir: new File(dir, "gradlehome"),
allowExistingRepo: true,
repo: "https://github.com/FabricMC/fabric.git",
commit: "71b634e5b7845296b11be3fa6545f4fbfacc017f",
patch: "fabric_api"
)
def timeStart = new Date()
def result = gradle.run(tasks: ["clean", "build"], args: ["--parallel", "-x", "check", "-x", "test", "-x", ":fabric-data-generation-api-v1:runDatagen", "-x", "javadoc"])
def timeStop = new Date()
TimeDuration duration = TimeCategory.minus(timeStop, timeStart)
println(duration)
}
static void main(String[] args) {
getInstance().run(new File(args[0]))
System.exit(0)
}
}

View File

@ -29,7 +29,7 @@ import net.fabricmc.loom.configuration.providers.mappings.intermediary.Intermedi
class IntermediaryMappingLayerTest extends LayeredMappingsSpecification { class IntermediaryMappingLayerTest extends LayeredMappingsSpecification {
def "Read intermediary mappings" () { def "Read intermediary mappings" () {
setup: setup:
mockMappingsProvider.intermediaryTinyFile() >> extractFileFromZip(downloadFile(INTERMEDIARY_1_17_URL, "intermediary.jar"), "mappings/mappings.tiny") intermediaryUrl = INTERMEDIARY_1_17_URL
when: when:
def mappings = getSingleMapping(new IntermediaryMappingsSpec()) def mappings = getSingleMapping(new IntermediaryMappingsSpec())
def tiny = getTiny(mappings) def tiny = getTiny(mappings)

View File

@ -24,15 +24,14 @@
package net.fabricmc.loom.test.unit.layeredmappings package net.fabricmc.loom.test.unit.layeredmappings
import groovy.transform.CompileStatic
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsProcessor
import net.fabricmc.loom.api.mappings.layered.MappingContext import net.fabricmc.loom.api.mappings.layered.MappingContext
import net.fabricmc.loom.api.mappings.layered.MappingLayer import net.fabricmc.loom.api.mappings.layered.MappingLayer
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace import net.fabricmc.loom.api.mappings.layered.MappingsNamespace
import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider
import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec
import net.fabricmc.loom.configuration.providers.mappings.IntermediaryService
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec
import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsProcessor
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider
import net.fabricmc.mappingio.adapter.MappingDstNsReorder import net.fabricmc.mappingio.adapter.MappingDstNsReorder
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch
import net.fabricmc.mappingio.format.Tiny2Writer import net.fabricmc.mappingio.format.Tiny2Writer
@ -42,13 +41,13 @@ import org.gradle.api.logging.Logger
import spock.lang.Specification import spock.lang.Specification
import java.nio.file.Path import java.nio.file.Path
import java.util.function.Supplier
import java.util.zip.ZipFile import java.util.zip.ZipFile
abstract class LayeredMappingsSpecification extends Specification implements LayeredMappingsTestConstants { abstract class LayeredMappingsSpecification extends Specification implements LayeredMappingsTestConstants {
Logger mockLogger = Mock(Logger) Logger mockLogger = Mock(Logger)
MappingsProvider mockMappingsProvider = Mock(MappingsProvider)
MinecraftProvider mockMinecraftProvider = Mock(MinecraftProvider) MinecraftProvider mockMinecraftProvider = Mock(MinecraftProvider)
String intermediaryUrl
MappingContext mappingContext = new TestMappingContext() MappingContext mappingContext = new TestMappingContext()
File tempDir = File.createTempDir() File tempDir = File.createTempDir()
@ -102,7 +101,12 @@ abstract class LayeredMappingsSpecification extends Specification implements Lay
return reorderedMappings return reorderedMappings
} }
@CompileStatic def setup() {
mockMinecraftProvider.file(_) >> { args ->
return new File(tempDir, args[0])
}
}
class TestMappingContext implements MappingContext { class TestMappingContext implements MappingContext {
@Override @Override
Path resolveDependency(Dependency dependency) { Path resolveDependency(Dependency dependency) {
@ -116,8 +120,10 @@ abstract class LayeredMappingsSpecification extends Specification implements Lay
} }
@Override @Override
MappingsProvider mappingsProvider() { Supplier<MemoryMappingTree> intermediaryTree() {
return mockMappingsProvider return {
IntermediaryService.create(intermediaryUrl, minecraftProvider()).memoryMappingTree
}
} }
@Override @Override

View File

@ -30,7 +30,7 @@ import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsS
class MojangMappingLayerTest extends LayeredMappingsSpecification { class MojangMappingLayerTest extends LayeredMappingsSpecification {
def "Read mojang mappings with synthetic field names" () { def "Read mojang mappings with synthetic field names" () {
setup: setup:
mockMappingsProvider.intermediaryTinyFile() >> extractFileFromZip(downloadFile(INTERMEDIARY_1_17_URL, "intermediary.jar"), "mappings/mappings.tiny") intermediaryUrl = INTERMEDIARY_1_17_URL
mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_17 mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_17
when: when:
def mappings = getLayeredMappings( def mappings = getLayeredMappings(
@ -50,7 +50,7 @@ class MojangMappingLayerTest extends LayeredMappingsSpecification {
def "Read mojang mappings without synthetic field names" () { def "Read mojang mappings without synthetic field names" () {
setup: setup:
mockMappingsProvider.intermediaryTinyFile() >> extractFileFromZip(downloadFile(INTERMEDIARY_1_17_URL, "intermediary.jar"), "mappings/mappings.tiny") intermediaryUrl = INTERMEDIARY_1_17_URL
mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_17 mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_17
when: when:
def mappings = getLayeredMappings( def mappings = getLayeredMappings(

View File

@ -32,7 +32,7 @@ import net.fabricmc.loom.configuration.providers.mappings.parchment.ParchmentMap
class ParchmentMappingLayerTest extends LayeredMappingsSpecification { class ParchmentMappingLayerTest extends LayeredMappingsSpecification {
def "Read parchment mappings" () { def "Read parchment mappings" () {
setup: setup:
mockMappingsProvider.intermediaryTinyFile() >> extractFileFromZip(downloadFile(INTERMEDIARY_1_16_5_URL, "intermediary.jar"), "mappings/mappings.tiny") intermediaryUrl = INTERMEDIARY_1_16_5_URL
mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_16_5 mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_16_5
when: when:
withMavenFile(PARCHMENT_NOTATION, downloadFile(PARCHMENT_URL, "parchment.zip")) withMavenFile(PARCHMENT_NOTATION, downloadFile(PARCHMENT_URL, "parchment.zip"))
@ -55,7 +55,7 @@ class ParchmentMappingLayerTest extends LayeredMappingsSpecification {
def "Read parchment mappings remove prefix" () { def "Read parchment mappings remove prefix" () {
setup: setup:
mockMappingsProvider.intermediaryTinyFile() >> extractFileFromZip(downloadFile(INTERMEDIARY_1_16_5_URL, "intermediary.jar"), "mappings/mappings.tiny") intermediaryUrl = INTERMEDIARY_1_16_5_URL
mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_16_5 mockMinecraftProvider.getVersionInfo() >> VERSION_META_1_16_5
when: when:
withMavenFile(PARCHMENT_NOTATION, downloadFile(PARCHMENT_URL, "parchment.zip")) withMavenFile(PARCHMENT_NOTATION, downloadFile(PARCHMENT_URL, "parchment.zip"))

View File

@ -65,6 +65,10 @@ trait GradleProjectTestTrait {
String repo = options.repo String repo = options.repo
String commit = options.commit String commit = options.commit
if (options.allowExistingRepo && projectDir.listFiles()?.length > 0) {
return
}
exec(projectDir, "git", "clone", repo, ".") exec(projectDir, "git", "clone", repo, ".")
exec(projectDir, "git", "checkout", commit) exec(projectDir, "git", "checkout", commit)
@ -85,6 +89,7 @@ trait GradleProjectTestTrait {
} }
private void exec(File projectDir, String... args) { private void exec(File projectDir, String... args) {
projectDir.mkdirs()
def process = args.execute([], projectDir) def process = args.execute([], projectDir)
process.consumeProcessOutput(System.out, System.err) process.consumeProcessOutput(System.out, System.err)

View File

@ -90,9 +90,8 @@ shadowJar {
} }
remapJar { remapJar {
dependsOn(shadowJar)
archiveClassifier.set("universal") archiveClassifier.set("universal")
input.fileValue(tasks["shadowJar"].outputs.files.singleFile) inputFile = shadowJar.archiveFile
} }
jar { jar {