From d40241d75aa4065bcb3e2f9378f890fdd5bb1bba Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Fri, 14 Jan 2022 19:50:45 +0000 Subject: [PATCH] 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. --- build.gradle | 20 +- .../fabricmc/loom/LoomGradleExtension.java | 6 - .../api/decompilers/DecompilerOptions.java | 8 +- .../api/mappings/layered/MappingContext.java | 5 +- .../mixin/AnnotationProcessorInvoker.java | 3 +- .../configuration/CompileConfiguration.java | 8 +- .../mappings/GradleMappingContext.java | 6 +- .../mappings/IntermediaryService.java | 118 +++++++ .../mappings/MappingsProviderImpl.java | 304 ++++++------------ .../IntermediaryMappingLayer.java | 13 +- .../IntermediaryMappingsSpec.java | 2 +- .../mappings/tiny/MappingsMerger.java | 110 +++++++ .../providers/mappings/tiny/TinyJarInfo.java | 55 ++++ .../decompilers/DecompilerConfiguration.java | 2 +- .../extension/LoomGradleExtensionImpl.java | 16 - .../loom/task/PrepareJarRemapTask.java | 108 +++++++ .../net/fabricmc/loom/task/RemapJarTask.java | 98 +++--- .../loom/task/RemapSourcesJarTask.java | 8 +- .../loom/task/RemapTaskConfiguration.java | 6 +- .../loom/task/service/MappingsService.java | 62 ++-- .../task/service/MixinMappingsService.java | 69 ++++ .../task/service/SourceRemapperService.java | 87 ++--- .../task/service/TinyRemapperService.java | 142 ++++++++ .../net/fabricmc/loom/util/DownloadUtil.java | 2 +- .../loom/util/TinyRemapperHelper.java | 24 +- .../loom/util/service/SharedService.java | 34 ++ .../util/service/SharedServiceManager.java | 110 +++++++ .../util/service/UnsafeWorkQueueHelper.java | 60 ++++ .../loom/test/LoomTestConstants.groovy | 2 +- .../test/benchmark/FabricAPIBenchmark.groovy | 63 ++++ .../IntermediaryMappingLayerTest.groovy | 2 +- .../LayeredMappingsSpecification.groovy | 26 +- .../MojangMappingLayerTest.groovy | 4 +- .../ParchmentMappingLayerTest.groovy | 4 +- .../test/util/GradleProjectTestTrait.groovy | 5 + .../projects/mixinApAutoRefmap/build.gradle | 3 +- 36 files changed, 1179 insertions(+), 416 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryService.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/TinyJarInfo.java create mode 100644 src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java create mode 100644 src/main/java/net/fabricmc/loom/task/service/MixinMappingsService.java create mode 100644 src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java create mode 100644 src/main/java/net/fabricmc/loom/util/service/SharedService.java create mode 100644 src/main/java/net/fabricmc/loom/util/service/SharedServiceManager.java create mode 100644 src/main/java/net/fabricmc/loom/util/service/UnsafeWorkQueueHelper.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy diff --git a/build.gradle b/build.gradle index 775cd0b9..653b0dff 100644 --- a/build.gradle +++ b/build.gradle @@ -114,14 +114,8 @@ jar { from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) } } -task sourcesJar(type: Jar, dependsOn: classes) { - archiveClassifier = 'sources' - from sourceSets.main.allSource -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - archiveClassifier = 'javadoc' - from javadoc.destinationDir +java { + withSourcesJar() } spotless { @@ -197,10 +191,7 @@ publishing { artifactId project.archivesBaseName version project.version - from components['java'] - - artifact sourcesJar - artifact javadocJar + from components.java pom.withXml { patchPom(asNode()) @@ -213,10 +204,7 @@ publishing { artifactId project.archivesBaseName version baseVersion + '-SNAPSHOT' - from components['java'] - - artifact sourcesJar - artifact javadocJar + from components.java pom.withXml { patchPom(asNode()) diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index b8929f31..5514a79f 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -24,7 +24,6 @@ package net.fabricmc.loom; -import java.io.File; import java.nio.file.Path; import java.util.List; import java.util.function.Supplier; @@ -37,7 +36,6 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; -import org.gradle.api.tasks.SourceSet; import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; @@ -112,10 +110,6 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace); - File getMixinMappings(SourceSet sourceSet); - - FileCollection getAllMixinMappings(); - boolean isRootProject(); default String getIntermediaryUrl(String minecraftVersion) { diff --git a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java index 5f278f5a..9cf58d6e 100644 --- a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java +++ b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java @@ -37,7 +37,7 @@ public abstract class DecompilerOptions implements Named { /** * Class name for to the {@link LoomDecompiler}. */ - public abstract Property getDecompilerClassname(); + public abstract Property getDecompilerClassName(); /** * Additional classpath entries for the decompiler jvm. @@ -60,7 +60,7 @@ public abstract class DecompilerOptions implements Named { public abstract Property getMaxThreads(); public DecompilerOptions() { - getDecompilerClassname().finalizeValueOnRead(); + getDecompilerClassName().finalizeValueOnRead(); getClasspath().finalizeValueOnRead(); getOptions().finalizeValueOnRead(); getMemory().convention(4096L).finalizeValueOnRead(); @@ -71,9 +71,9 @@ public abstract class DecompilerOptions implements Named { public record Dto(String className, Map options, int maxThreads) implements Serializable { } 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( - getDecompilerClassname().get(), + getDecompilerClassName().get(), getOptions().get(), getMaxThreads().get() ); diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingContext.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingContext.java index aaa7e500..ffb96d59 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingContext.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingContext.java @@ -25,13 +25,14 @@ package net.fabricmc.loom.api.mappings.layered; import java.nio.file.Path; +import java.util.function.Supplier; import org.gradle.api.artifacts.Dependency; import org.gradle.api.logging.Logger; import org.jetbrains.annotations.ApiStatus; -import net.fabricmc.loom.configuration.providers.mappings.MappingsProvider; 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 */ public interface MappingContext { @@ -39,7 +40,7 @@ public interface MappingContext { Path resolveMavenDependency(String mavenNotation); - MappingsProvider mappingsProvider(); + Supplier intermediaryTree(); MinecraftProvider minecraftProvider(); diff --git a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java index 73780504..b8ba4c39 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -42,6 +42,7 @@ import org.gradle.api.tasks.SourceSet; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; import net.fabricmc.loom.extension.MixinExtension; +import net.fabricmc.loom.task.service.MixinMappingsService; import net.fabricmc.loom.util.Constants; /** @@ -84,7 +85,7 @@ public abstract class AnnotationProcessorInvoker { String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get(); Map args = new HashMap<>() {{ 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.DEFAULT_OBFUSCATION_ENV, "named:intermediary"); }}; diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 35f7050e..0204278f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -193,15 +193,15 @@ public final class CompileConfiguration { 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); extension.setMinecraftProvider(minecraftProvider); minecraftProvider.provide(); - // Provide the mappings - final MappingsProviderImpl mappingsProvider = new MappingsProviderImpl(project, minecraftProvider); + final DependencyInfo mappingsDep = DependencyInfo.create(project, Constants.Configurations.MAPPINGS); + final MappingsProviderImpl mappingsProvider = MappingsProviderImpl.getInstance(project, mappingsDep, minecraftProvider); extension.setMappingsProvider(mappingsProvider); - mappingsProvider.provide(); + mappingsProvider.applyToProject(project, mappingsDep); // Provide the remapped mc jars final IntermediaryMinecraftProvider intermediaryMinecraftProvider; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java index 1cf907d1..e2a83da6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.configuration.providers.mappings; import java.io.File; import java.nio.file.Path; +import java.util.function.Supplier; import org.gradle.api.Project; 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.api.mappings.layered.MappingContext; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.mappingio.tree.MemoryMappingTree; public class GradleMappingContext implements MappingContext { private final Project project; @@ -62,8 +64,8 @@ public class GradleMappingContext implements MappingContext { } @Override - public MappingsProvider mappingsProvider() { - return extension.getMappingsProvider(); + public Supplier intermediaryTree() { + return () -> IntermediaryService.getInstance(project, minecraftProvider()).getMemoryMappingTree(); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryService.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryService.java new file mode 100644 index 00000000..bd0c4e90 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryService.java @@ -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 = 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"); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index e56cbfd8..6100d546 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -28,107 +28,135 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.Reader; -import java.net.URL; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.Arrays; -import java.util.Collections; import java.util.Map; import java.util.Objects; -import java.util.regex.Pattern; +import java.util.function.Supplier; -import com.google.common.base.Stopwatch; -import com.google.common.net.UrlEscapers; +import com.google.common.base.Suppliers; import com.google.gson.JsonObject; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; import org.jetbrains.annotations.Nullable; 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.api.mappings.layered.MappingsNamespace; 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.MinecraftProvider; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DeletingFileVisitor; -import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.service.SharedService; +import net.fabricmc.loom.util.service.SharedServiceManager; 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.Tiny2Reader; -import net.fabricmc.mappingio.format.Tiny2Writer; -import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.stitch.Command; import net.fabricmc.stitch.commands.CommandProposeFieldNames; -public class MappingsProviderImpl implements MappingsProvider { - public String mappingsIdentifier; +public class MappingsProviderImpl implements MappingsProvider, SharedService { + private static final Logger LOGGER = LoggerFactory.getLogger(MappingsProviderImpl.class); - private Path mappingsWorkingDir; - private Path intermediaryTiny; - private boolean hasRefreshed = false; + private Supplier mappingTree; + public final String mappingsIdentifier; + + private final Path mappingsWorkingDir; // The mappings that gradle gives us - private Path baseTinyMappings; + private final Path baseTinyMappings; // The mappings we use in practice - public Path tinyMappings; - public Path tinyMappingsJar; - private Path unpickDefinitions; + public final Path tinyMappings; + public final Path tinyMappingsJar; + private final Path unpickDefinitions; + private boolean hasUnpickDefinitions; private UnpickMetadata unpickMetadata; - private MemoryMappingTree mappingTree; private Map signatureFixes; - private final Project project; - private final MinecraftProvider minecraftProvider; - private final LoomGradleExtension extension; - public MappingsProviderImpl(Project project, MinecraftProvider minecraftProvider) { - this.project = project; - this.minecraftProvider = minecraftProvider; - this.extension = LoomGradleExtension.get(project); + private final Supplier intermediaryService; + + private MappingsProviderImpl(String mappingsIdentifier, Path mappingsWorkingDir, Supplier intermediaryService) { + this.mappingsIdentifier = mappingsIdentifier; + + this.mappingsWorkingDir = mappingsWorkingDir; + 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 = Suppliers.memoize(() -> IntermediaryService.getInstance(project, minecraftProvider)); + return create(dependency, minecraftProvider, intermediaryService); + }); } 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 { - final DependencyInfo dependency = DependencyInfo.create(project, Constants.Configurations.MAPPINGS); + private static MappingsProviderImpl create(DependencyInfo dependency, MinecraftProvider minecraftProvider, Supplier intermediaryService) { + 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(); - File mappingsJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not find yarn mappings: " + dependency)); + final String mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, jarInfo.v2()), minecraftProvider.minecraftVersion()); + final Path workingDir = minecraftProvider.dir(mappingsIdentifier).toPath(); - String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged"); - boolean isV2 = isV2(dependency, mappingsJar); - this.mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, isV2)); + var mappingProvider = new MappingsProviderImpl(mappingsIdentifier, workingDir, intermediaryService); - 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()) { - storeMappings(project, minecraftProvider, mappingsJar.toPath()); + storeMappings(minecraftProvider, inputJar); } else { - try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) { + try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) { extractExtras(fileSystem); } } - mappingTree = readMappings(); - if (Files.notExists(tinyMappingsJar) || isRefreshDeps()) { Files.deleteIfExists(tinyMappingsJar); ZipUtils.add(tinyMappingsJar, "mappings/mappings.tiny", Files.readAllBytes(tinyMappings)); } + mappingTree = Suppliers.memoize(this::readMappings); + } + + public void applyToProject(Project project, DependencyInfo dependency) { if (hasUnpickDefinitions()) { String notation = String.format("%s:%s:%s:constants", dependency.getDependency().getGroup(), @@ -137,13 +165,13 @@ public class MappingsProviderImpl implements MappingsProvider { ); project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation); - populateUnpickClasspath(); + populateUnpickClasspath(project); } 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(":"); if (depStringSplit.length >= 4) { @@ -153,42 +181,22 @@ public class MappingsProviderImpl implements MappingsProvider { return isV2 ? "-v2" : ""; } - private boolean isV2(DependencyInfo dependency, File mappingsJar) throws IOException { - String minecraftVersion = minecraftProvider.minecraftVersion(); + private void storeMappings(MinecraftProvider minecraftProvider, Path inputJar) throws IOException { + 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 - 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)) { + try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) { extractMappings(fileSystem, baseTinyMappings); extractExtras(fileSystem); } if (areMappingsV2(baseTinyMappings)) { // These are unmerged v2 mappings - mergeAndSaveMappings(project, baseTinyMappings, tinyMappings); + MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediaryService.get()); } else { if (minecraftProvider instanceof MergedMinecraftProvider mergedMinecraftProvider) { // These are merged v1 mappings Files.deleteIfExists(tinyMappings); - project.getLogger().lifecycle(":populating field names"); + LOGGER.info(":populating field names"); suggestFieldNames(mergedMinecraftProvider, baseTinyMappings, tinyMappings); } else { throw new UnsupportedOperationException("V1 mappings only support merged minecraft"); @@ -196,10 +204,14 @@ public class MappingsProviderImpl implements MappingsProvider { } } - private MemoryMappingTree readMappings() throws IOException { - MemoryMappingTree mappingTree = new MemoryMappingTree(); - MappingReader.read(tinyMappings, mappingTree); - return mappingTree; + private MemoryMappingTree readMappings() { + try { + MemoryMappingTree mappingTree = new MemoryMappingTree(); + MappingReader.read(tinyMappings, mappingTree); + return mappingTree; + } catch (IOException e) { + throw new UncheckedIOException("Failed to read mappings", e); + } } 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 { - 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 { + public static void extractMappings(Path jar, Path extractTo) throws IOException { try (FileSystem unmergedIntermediaryFs = FileSystems.newFileSystem(jar, (ClassLoader) null)) { extractMappings(unmergedIntermediaryFs, extractTo); } @@ -271,7 +275,7 @@ public class MappingsProviderImpl implements MappingsProvider { ); } - private void populateUnpickClasspath() { + private void populateUnpickClasspath(Project project) { String unpickCliName = "unpick-cli"; project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH, 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) { Command command = new CommandProposeFieldNames(); runCommand(command, minecraftProvider.getMergedJar().getAbsolutePath(), @@ -377,19 +311,7 @@ public class MappingsProviderImpl implements MappingsProvider { } } - private void initFiles() { - 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() { + private static void cleanWorkingDirectory(Path mappingsWorkingDir) { try { if (Files.exists(mappingsWorkingDir)) { 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 public Path 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 // 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() { @@ -448,15 +356,6 @@ public class MappingsProviderImpl implements MappingsProvider { 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) { 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) { } - protected boolean isRefreshDeps() { + protected static boolean isRefreshDeps() { return LoomGradlePlugin.refreshDeps; } + + @Override + public void close() throws IOException { + mappingTree = null; + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java index a6c06ba9..cfd0ee32 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingLayer.java @@ -24,20 +24,17 @@ package net.fabricmc.loom.configuration.providers.mappings.intermediary; -import java.io.BufferedReader; -import java.io.File; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.util.Collections; +import java.util.function.Supplier; import net.fabricmc.loom.api.mappings.layered.MappingLayer; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.mappingio.MappingVisitor; 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) implements MappingLayer { @Override public MappingsNamespace getSourceNamespace() { return MappingsNamespace.OFFICIAL; @@ -48,8 +45,6 @@ public record IntermediaryMappingLayer(File tinyFile) implements MappingLayer { // Populate named with intermediary and add Add a "named" namespace MappingNsCompleter nsCompleter = new MappingNsCompleter(mappingVisitor, Collections.singletonMap(MappingsNamespace.NAMED.toString(), MappingsNamespace.INTERMEDIARY.toString()), true); - try (BufferedReader reader = Files.newBufferedReader(tinyFile().toPath(), StandardCharsets.UTF_8)) { - Tiny2Reader.read(reader, nsCompleter); - } + memoryMappingTree.get().accept(nsCompleter); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java index 194b3038..0b134474 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/intermediary/IntermediaryMappingsSpec.java @@ -30,6 +30,6 @@ import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec; public record IntermediaryMappingsSpec() implements MappingsSpec { @Override public IntermediaryMappingLayer createLayer(MappingContext context) { - return new IntermediaryMappingLayer(context.mappingsProvider().intermediaryTinyFile()); + return new IntermediaryMappingLayer(context.intermediaryTree()); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java new file mode 100644 index 00000000..86aa7afa --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/MappingsMerger.java @@ -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; + } + } + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/TinyJarInfo.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/TinyJarInfo.java new file mode 100644 index 00000000..1a6649a4 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/tiny/TinyJarInfo.java @@ -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 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; + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java index 3a75bfd4..8bc63e20 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java +++ b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java @@ -41,6 +41,6 @@ public final class DecompilerConfiguration { } private static void registerDecompiler(Project project, String name, Class 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())); } } diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java index 55541ecd..d805dcb8 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java @@ -24,7 +24,6 @@ package net.fabricmc.loom.extension; -import java.io.File; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; @@ -41,7 +40,6 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; -import org.gradle.api.tasks.SourceSet; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; @@ -60,7 +58,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen private final LoomFiles loomFiles; private final ConfigurableFileCollection unmappedMods; - private final ConfigurableFileCollection mixinMappings; private final MappingSet[] srcMappingCache = new MappingSet[2]; private final Mercury[] srcMercuryCache = new Mercury[2]; private final Map> lazyConfigurations = new HashMap<>(); @@ -79,7 +76,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen this.project = project; // Initiate with newInstance to allow gradle to decorate our extension this.mixinApExtension = project.getObjects().newInstance(MixinExtensionImpl.class, project); - this.mixinMappings = project.getObjects().fileCollection(); this.loomFiles = files; this.unmappedMods = project.files(); } @@ -94,18 +90,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen 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 public void setDependencyManager(LoomDependencyManager dependencyManager) { this.dependencyManager = dependencyManager; diff --git a/src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java b/src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java new file mode 100644 index 00000000..81b2cedf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java @@ -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 getTinyRemapperBuildServiceUuid(); + Property getInputTagName(); + RegularFileProperty getInputFile(); + } + + public abstract static class ReadInputsAction implements WorkAction { + 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); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index a9d357aa..efe5815e 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -26,7 +26,6 @@ package net.fabricmc.loom.task; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.io.Serializable; import java.nio.file.Files; @@ -34,12 +33,14 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; import java.util.jar.Manifest; import java.util.stream.Collectors; import javax.inject.Inject; import com.google.common.base.Preconditions; +import com.google.common.base.Suppliers; import com.google.gson.JsonObject; import org.gradle.api.artifacts.Configuration; 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.provider.ListProperty; import org.gradle.api.provider.Property; -import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; -import org.objectweb.asm.commons.Remapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,20 +60,18 @@ import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerRemapper; import net.fabricmc.accesswidener.AccessWidenerWriter; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.build.MixinRefmapHelper; import net.fabricmc.loom.build.nesting.IncludedJarFactory; import net.fabricmc.loom.build.nesting.JarNester; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.extension.MixinExtension; 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.ZipUtils; -import net.fabricmc.tinyremapper.InputTag; +import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper; import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.TinyRemapper; -import net.fabricmc.tinyremapper.TinyUtils; public abstract class RemapJarTask extends AbstractRemapJarTask { private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; @@ -84,6 +82,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { @Input public abstract Property getAddNestedDependencies(); + private Supplier tinyRemapperService = Suppliers.memoize(() -> TinyRemapperService.getOrCreate(this)); + @Inject public RemapJarTask() { super(); @@ -93,6 +93,25 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { Configuration includeConfiguration = getProject().getConfigurations().getByName(Constants.Configurations.INCLUDE); 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 @@ -105,14 +124,14 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { } params.getJarManifestService().set(JarManifestService.get(getProject())); + params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getProject(), tinyRemapperService.get())); 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(); params.getUseMixinExtension().set(!legacyMixin); if (legacyMixin) { - params.getMixinMappings().from(extension.getAllMixinMappings()); setupLegacyMixinRefmapRemapping(params); } }); @@ -174,8 +193,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { public interface RemapParams extends AbstractRemapParams { ConfigurableFileCollection getNestedJars(); ConfigurableFileCollection getRemapClasspath(); - ConfigurableFileCollection getMixinMappings(); - ListProperty> getMappings(); + Property getInputTagName(); Property getUseMixinExtension(); @@ -183,19 +201,25 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { ListProperty getMixinData(); Property getJarManifestService(); + Property getTinyRemapperBuildServiceUuid(); } public abstract static class RemapAction extends AbstractRemapAction { private static final Logger LOGGER = LoggerFactory.getLogger(RemapAction.class); + private final TinyRemapperService tinyRemapperService; private TinyRemapper tinyRemapper; + public RemapAction() { + this.tinyRemapperService = UnsafeWorkQueueHelper.get(getParameters().getTinyRemapperBuildServiceUuid(), TinyRemapperService.class); + } + @Override public void execute() { try { LOGGER.info("Remapping {} to {}", inputFile, outputFile); - tinyRemapper = createTinyRemapper(); + tinyRemapper = tinyRemapperService.getTinyRemapperForRemapping(); remap(); remapAccessWidener(); @@ -204,9 +228,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { modifyJarManifest(); rewriteJar(); - tinyRemapper.finish(); - tinyRemapper = null; - LOGGER.debug("Finished remapping {}", inputFile); } catch (Exception e) { try { @@ -220,13 +241,9 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { } private void remap() throws IOException { - final InputTag inputTag = tinyRemapper.createInputTag(); - - tinyRemapper.readInputsAsync(inputTag, inputFile); - try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(outputFile).build()) { 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; } - byte[] remapped = remapAccessWidener(accessWidenerFile.content(), tinyRemapper.getEnvironment().getRemapper(), MappingsNamespace.INTERMEDIARY.toString()); + byte[] remapped = remapAccessWidener(accessWidenerFile.content()); // Finally, replace the output with the remaped aw 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); AccessWidenerWriter writer = new AccessWidenerWriter(version); AccessWidenerRemapper remapper = new AccessWidenerRemapper( writer, - asmRemapper, - MappingsNamespace.NAMED.toString(), - targetNamespace + tinyRemapper.getEnvironment().getRemapper(), + getParameters().getSourceNamespace().get(), + getParameters().getTargetNamespace().get() ); AccessWidenerReader reader = new AccessWidenerReader(remapper); reader.read(input); @@ -300,30 +317,15 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { }))); } } + } - private TinyRemapper createTinyRemapper() { - TinyRemapper.Builder builder = TinyRemapper.newRemapper(); + @Internal + public TinyRemapperService getTinyRemapperService() { + return tinyRemapperService.get(); + } - for (Provider provider : getParameters().getMappings().get()) { - builder.withMappings(provider.get().getMappingsProvider()); - } - - for (File mixinMapping : getParameters().getMixinMappings()) { - builder.withMappings(TinyUtils.createTinyMappingProvider(mixinMapping.toPath(), getParameters().getSourceNamespace().get(), getParameters().getTargetNamespace().get())); - } - - if (getParameters().getUseMixinExtension().get()) { - builder.extension(new net.fabricmc.tinyremapper.extension.mixin.MixinExtension()); - } - - TinyRemapper remapper = builder.build(); - - // Apply classpath - for (File file : getParameters().getRemapClasspath()) { - remapper.readClassPathAsync(file.toPath()); - } - - return remapper; - } + @Internal + String getInputTagName() { + return getProject().getPath() + getName(); } } diff --git a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java index 824a6784..099e4ae6 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java @@ -35,8 +35,8 @@ import org.gradle.api.tasks.TaskAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.fabricmc.loom.task.service.MappingsService; import net.fabricmc.loom.task.service.SourceRemapperService; +import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper; public abstract class RemapSourcesJarTask extends AbstractRemapJarTask { @Inject @@ -49,12 +49,12 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask { @TaskAction public void run() { 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 { - Property getSourcesRemapperService(); + Property getSourcesRemapperServiceUuid(); } public abstract static class RemapSourcesAction extends AbstractRemapAction { @@ -65,7 +65,7 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask { public RemapSourcesAction() { super(); - sourceRemapperService = getParameters().getSourcesRemapperService().get(); + sourceRemapperService = UnsafeWorkQueueHelper.get(getParameters().getSourcesRemapperServiceUuid(), SourceRemapperService.class); } @Override diff --git a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java index 34d2cc8c..d1cfc0f2 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java +++ b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java @@ -54,8 +54,8 @@ public class RemapTaskConfiguration { return; } - // Register the default remap jar task - TaskProvider remapJarTaskProvider = tasks.register(REMAP_JAR_TASK_NAME, RemapJarTask.class, 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. + RemapJarTask remapJarTask = tasks.create(REMAP_JAR_TASK_NAME, RemapJarTask.class, task -> { final AbstractArchiveTask jarTask = tasks.named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).get(); // Basic task setup @@ -76,7 +76,7 @@ public class RemapTaskConfiguration { 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); diff --git a/src/main/java/net/fabricmc/loom/task/service/MappingsService.java b/src/main/java/net/fabricmc/loom/task/service/MappingsService.java index 99cebcb7..53e9bec4 100644 --- a/src/main/java/net/fabricmc/loom/task/service/MappingsService.java +++ b/src/main/java/net/fabricmc/loom/task/service/MappingsService.java @@ -24,49 +24,45 @@ package net.fabricmc.loom.task.service; -import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.file.Path; 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.configuration.providers.mappings.MappingsProviderImpl; 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.tree.MemoryMappingTree; import net.fabricmc.tinyremapper.IMappingProvider; -public abstract class MappingsService implements BuildService, AutoCloseable { - interface Params extends BuildServiceParameters { - RegularFileProperty getMappingsFile(); +public final class MappingsService implements SharedService { + private record Options(Path mappingsFile, String from, String to, boolean remapLocals) { } - Property getFromNamespace(); - Property getToNamespace(); - - Property getRemapLocals(); + public static MappingsService create(Project project, String name, Path mappingsFile, String from, String to, boolean remapLocals) { + return create(SharedServiceManager.get(project), name, mappingsFile, from, to, remapLocals); } - public static synchronized Provider create(Project project, String name, File mappingsFile, String from, String to, boolean remapLocals) { - return project.getGradle().getSharedServices().registerIfAbsent(name, MappingsService.class, spec -> { - spec.parameters(params -> { - params.getMappingsFile().set(mappingsFile); - params.getFromNamespace().set(from); - params.getToNamespace().set(to); - params.getRemapLocals().set(remapLocals); - }); - }); + public static synchronized MappingsService create(SharedServiceManager sharedServiceManager, String name, Path mappingsFile, String from, String to, boolean remapLocals) { + final Options options = new Options(mappingsFile, from, to, remapLocals); + final String id = name + options.hashCode(); + return sharedServiceManager.getOrCreateService(id, () -> new MappingsService(options)); } - public static Provider 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 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; @@ -76,13 +72,13 @@ public abstract class MappingsService implements BuildService 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); + } + }; + } +} diff --git a/src/main/java/net/fabricmc/loom/task/service/SourceRemapperService.java b/src/main/java/net/fabricmc/loom/task/service/SourceRemapperService.java index 891a909c..4955f068 100644 --- a/src/main/java/net/fabricmc/loom/task/service/SourceRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/SourceRemapperService.java @@ -24,49 +24,57 @@ package net.fabricmc.loom.task.service; +import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.function.Supplier; +import com.google.common.base.Suppliers; import org.cadixdev.lorenz.MappingSet; import org.cadixdev.mercury.Mercury; import org.cadixdev.mercury.remapper.MercuryRemapper; import org.gradle.api.Project; import org.gradle.api.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.LoggerFactory; +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.task.RemapSourcesJarTask; import net.fabricmc.loom.util.DeletingFileVisitor; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.SourceRemapper; 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; -public abstract class SourceRemapperService implements BuildService, AutoCloseable { - public interface Params extends BuildServiceParameters { - Property> getMappings(); +public final class SourceRemapperService implements SharedService { + public static synchronized SourceRemapperService create(RemapSourcesJarTask task) { + 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(); - } - - public static synchronized Provider create(Project project, Provider 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); - } - )); + return sharedServiceManager.getOrCreateService(id, () -> + new SourceRemapperService(MappingsService.createDefault(project, from, to), task.getClasspath() + )); } private static final Logger LOGGER = LoggerFactory.getLogger(SourceRemapperService.class); - private Mercury mercury; + private final MappingsService mappingsService; + private final ConfigurableFileCollection classpath; + + private final Supplier 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 { if (source.equals(destination)) { @@ -99,35 +107,34 @@ public abstract class SourceRemapperService implements BuildService mercury.getClassPath().add(file.toPath())); - } - + private synchronized void doRemap(Path srcPath, Path dstPath, Path source) { try { - // Not thread safe!! - mercury.rewrite(srcPath, dstPath); + synchronized (mercury) { + mercury.get().rewrite(srcPath, dstPath); + } } catch (Exception e) { LOGGER.warn("Could not remap " + source + " fully!", e); } } 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() { - return getParameters().getMappings().get().get(); - } + private Mercury createMercury() { + var mercury = new Mercury(); + mercury.setGracefulClasspathChecks(true); - @Override - public void close() throws Exception { - mercury = null; - // This is required (: - System.gc(); + try { + mercury.getProcessors().add(MercuryRemapper.create(getMappings())); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read mercury mappings", e); + } + + for (File file : classpath.getFiles()) { + mercury.getClassPath().add(file.toPath()); + } + + return mercury; } } diff --git a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java new file mode 100644 index 00000000..e8182110 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -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 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 inputTagMap = new ConcurrentHashMap<>(); + private final HashSet classpath = new HashSet<>(); + // Set to true once remapping has started, once set no inputs can be read. + private boolean isRemapping = false; + + public TinyRemapperService(List 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 paths) { + List 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; + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java index fcc79aa7..de9f800a 100644 --- a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java +++ b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java @@ -35,7 +35,7 @@ import java.util.zip.GZIPInputStream; import com.google.common.io.Files; import org.apache.commons.io.FileUtils; import org.gradle.api.Project; -import org.gradle.api.logging.Logger; +import org.slf4j.Logger; import net.fabricmc.loom.LoomGradlePlugin; diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index e0e6b188..de9c82e8 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -110,21 +110,31 @@ public final class TinyRemapperHelper { public static IMappingProvider create(MappingTree mappings, String from, String to, boolean remapLocalVariables) { return (acceptor) -> { + final int fromId = mappings.getNamespaceId(from); + final int toId = mappings.getNamespaceId(to); + for (MappingTree.ClassMapping classDef : mappings.getClasses()) { - String className = classDef.getName(from); - acceptor.acceptClass(className, classDef.getName(to)); + String className = classDef.getName(fromId); + 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()) { - 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()) { - IMappingProvider.Member methodIdentifier = memberOf(className, method.getName(from), method.getDesc(from)); - acceptor.acceptMethod(methodIdentifier, method.getName(to)); + IMappingProvider.Member methodIdentifier = memberOf(className, method.getName(fromId), method.getDesc(fromId)); + acceptor.acceptMethod(methodIdentifier, method.getName(toId)); if (remapLocalVariables) { for (MappingTree.MethodArgMapping parameter : method.getArgs()) { - String name = parameter.getName(to); + String name = parameter.getName(toId); if (name == null) { continue; @@ -136,7 +146,7 @@ public final class TinyRemapperHelper { for (MappingTree.MethodVarMapping localVariable : method.getVars()) { acceptor.acceptMethodVar(methodIdentifier, localVariable.getLvIndex(), localVariable.getStartOpIdx(), localVariable.getLvtRowIndex(), - localVariable.getName(to)); + localVariable.getName(toId)); } } } diff --git a/src/main/java/net/fabricmc/loom/util/service/SharedService.java b/src/main/java/net/fabricmc/loom/util/service/SharedService.java new file mode 100644 index 00000000..53cb2c7c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/service/SharedService.java @@ -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 { + } +} diff --git a/src/main/java/net/fabricmc/loom/util/service/SharedServiceManager.java b/src/main/java/net/fabricmc/loom/util/service/SharedServiceManager.java new file mode 100644 index 00000000..62f1e922 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/service/SharedServiceManager.java @@ -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 SERVICE_FACTORY_MAP = new ConcurrentHashMap<>(); + private final Gradle gradle; + + private final Map 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 getOrCreateService(String id, Supplier 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 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(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/service/UnsafeWorkQueueHelper.java b/src/main/java/net/fabricmc/loom/util/service/UnsafeWorkQueueHelper.java new file mode 100644 index 00000000..62faa290 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/service/UnsafeWorkQueueHelper.java @@ -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 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 get(Property property, Class 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; + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index 3aab4938..bae7f567 100644 --- a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy @@ -28,7 +28,7 @@ import org.gradle.util.GradleVersion class LoomTestConstants { 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] } diff --git a/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy b/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy new file mode 100644 index 00000000..a19b35be --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy @@ -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) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/IntermediaryMappingLayerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/IntermediaryMappingLayerTest.groovy index 90565823..0f6c4161 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/IntermediaryMappingLayerTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/IntermediaryMappingLayerTest.groovy @@ -29,7 +29,7 @@ import net.fabricmc.loom.configuration.providers.mappings.intermediary.Intermedi class IntermediaryMappingLayerTest extends LayeredMappingsSpecification { def "Read intermediary mappings" () { setup: - mockMappingsProvider.intermediaryTinyFile() >> extractFileFromZip(downloadFile(INTERMEDIARY_1_17_URL, "intermediary.jar"), "mappings/mappings.tiny") + intermediaryUrl = INTERMEDIARY_1_17_URL when: def mappings = getSingleMapping(new IntermediaryMappingsSpec()) def tiny = getTiny(mappings) diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsSpecification.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsSpecification.groovy index fb682434..e0ce582b 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsSpecification.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/LayeredMappingsSpecification.groovy @@ -24,15 +24,14 @@ 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.MappingLayer 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.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.MappingSourceNsSwitch import net.fabricmc.mappingio.format.Tiny2Writer @@ -42,13 +41,13 @@ import org.gradle.api.logging.Logger import spock.lang.Specification import java.nio.file.Path +import java.util.function.Supplier import java.util.zip.ZipFile abstract class LayeredMappingsSpecification extends Specification implements LayeredMappingsTestConstants { Logger mockLogger = Mock(Logger) - MappingsProvider mockMappingsProvider = Mock(MappingsProvider) MinecraftProvider mockMinecraftProvider = Mock(MinecraftProvider) - + String intermediaryUrl MappingContext mappingContext = new TestMappingContext() File tempDir = File.createTempDir() @@ -102,7 +101,12 @@ abstract class LayeredMappingsSpecification extends Specification implements Lay return reorderedMappings } - @CompileStatic + def setup() { + mockMinecraftProvider.file(_) >> { args -> + return new File(tempDir, args[0]) + } + } + class TestMappingContext implements MappingContext { @Override Path resolveDependency(Dependency dependency) { @@ -116,8 +120,10 @@ abstract class LayeredMappingsSpecification extends Specification implements Lay } @Override - MappingsProvider mappingsProvider() { - return mockMappingsProvider + Supplier intermediaryTree() { + return { + IntermediaryService.create(intermediaryUrl, minecraftProvider()).memoryMappingTree + } } @Override diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/MojangMappingLayerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/MojangMappingLayerTest.groovy index aad3661a..9a55075b 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/MojangMappingLayerTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/MojangMappingLayerTest.groovy @@ -30,7 +30,7 @@ import net.fabricmc.loom.configuration.providers.mappings.mojmap.MojangMappingsS class MojangMappingLayerTest extends LayeredMappingsSpecification { def "Read mojang mappings with synthetic field names" () { 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 when: def mappings = getLayeredMappings( @@ -50,7 +50,7 @@ class MojangMappingLayerTest extends LayeredMappingsSpecification { def "Read mojang mappings without synthetic field names" () { 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 when: def mappings = getLayeredMappings( diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/ParchmentMappingLayerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/ParchmentMappingLayerTest.groovy index 2c3dc3dc..c126c3dd 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/ParchmentMappingLayerTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/layeredmappings/ParchmentMappingLayerTest.groovy @@ -32,7 +32,7 @@ import net.fabricmc.loom.configuration.providers.mappings.parchment.ParchmentMap class ParchmentMappingLayerTest extends LayeredMappingsSpecification { def "Read parchment mappings" () { 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 when: withMavenFile(PARCHMENT_NOTATION, downloadFile(PARCHMENT_URL, "parchment.zip")) @@ -55,7 +55,7 @@ class ParchmentMappingLayerTest extends LayeredMappingsSpecification { def "Read parchment mappings remove prefix" () { 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 when: withMavenFile(PARCHMENT_NOTATION, downloadFile(PARCHMENT_URL, "parchment.zip")) diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy index bd68fd93..489d17a5 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy @@ -65,6 +65,10 @@ trait GradleProjectTestTrait { String repo = options.repo String commit = options.commit + if (options.allowExistingRepo && projectDir.listFiles()?.length > 0) { + return + } + exec(projectDir, "git", "clone", repo, ".") exec(projectDir, "git", "checkout", commit) @@ -85,6 +89,7 @@ trait GradleProjectTestTrait { } private void exec(File projectDir, String... args) { + projectDir.mkdirs() def process = args.execute([], projectDir) process.consumeProcessOutput(System.out, System.err) diff --git a/src/test/resources/projects/mixinApAutoRefmap/build.gradle b/src/test/resources/projects/mixinApAutoRefmap/build.gradle index e425043f..610d042d 100644 --- a/src/test/resources/projects/mixinApAutoRefmap/build.gradle +++ b/src/test/resources/projects/mixinApAutoRefmap/build.gradle @@ -90,9 +90,8 @@ shadowJar { } remapJar { - dependsOn(shadowJar) archiveClassifier.set("universal") - input.fileValue(tasks["shadowJar"].outputs.files.singleFile) + inputFile = shadowJar.archiveFile } jar {