diff --git a/.editorconfig b/.editorconfig index e1774508..ef62c9e2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -[*.{gradle,java}] +[*.{gradle,java,kotlin}] indent_style = tab ij_continuation_indent_size = 8 ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|,net.fabricmc.** diff --git a/build.gradle b/build.gradle index cbb54702..138eb3e8 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ plugins { id 'checkstyle' id 'jacoco' id 'codenarc' + id "org.jetbrains.kotlin.jvm" version "1.5.31" // Must match the version included with gradle. id "com.diffplug.spotless" version "5.14.1" id 'net.kyori.blossom' version '1.3.0' id 'me.shedaniel.java-version-bridge' version '1.0-SNAPSHOT' @@ -21,6 +22,12 @@ tasks.withType(JavaCompile).configureEach { it.options.release = 17 } +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + jvmTarget = "16" // Change to 17 when updating gradle/kotlin to 1.6.10 + } +} + group = "dev.architectury" archivesBaseName = project.name def baseVersion = '0.11.0' @@ -108,6 +115,11 @@ dependencies { implementation ('dev.architectury:mercury:0.1.1.11') + // Kotlin + implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.4.1") { + transitive = false + } + // Kapt integration compileOnly('org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0') @@ -124,7 +136,10 @@ dependencies { testImplementation('org.spockframework:spock-core:2.0-groovy-3.0') { exclude module: 'groovy-all' } - testImplementation 'io.javalin:javalin:3.13.11' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + testImplementation ('io.javalin:javalin:3.13.11') { + exclude group: 'org.jetbrains.kotlin' + } testImplementation 'net.fabricmc:fabric-installer:0.9.0' compileOnly 'org.jetbrains:annotations:23.0.0' @@ -157,14 +172,8 @@ task downgradeJava(type: BridgeTransformingTask, dependsOn: mainJar) { } } -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 { @@ -176,6 +185,13 @@ spotless { groovy { licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-") } + + kotlin { + licenseHeaderFile(rootProject.file("HEADER")).yearSeparator("-") + targetExclude("**/build.gradle.kts") + targetExclude("src/test/resources/projects/*/**") + ktlint() + } } checkstyle { @@ -242,8 +258,6 @@ publishing { from components.java artifact downgradeJava - artifact sourcesJar - artifact javadocJar } if (isSnapshot) return @@ -255,8 +269,6 @@ publishing { from components.java artifact downgradeJava - artifact sourcesJar - artifact javadocJar } pluginSnapshot(MavenPublication) { @@ -279,7 +291,7 @@ publishing { } } } - + repositories { if (System.getenv("MAVEN_PASS") != null) { maven { diff --git a/gradle.properties b/gradle.properties index 69c68a84..31014b15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,5 @@ name = fabric-loom description = The Gradle plugin for Fabric -url = https://github.com/FabricMC/fabric-loom \ No newline at end of file +url = https://github.com/FabricMC/fabric-loom + +kotlin.stdlib.default.dependency = false \ No newline at end of file diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index fe8add4a..e710e511 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -24,7 +24,7 @@ package net.fabricmc.loom; -import java.io.File; +import java.nio.file.Path; import java.util.List; import java.util.function.Supplier; @@ -36,9 +36,9 @@ 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; import net.fabricmc.loom.configuration.InstallerData; import net.fabricmc.loom.configuration.LoomDependencyManager; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; @@ -52,7 +52,9 @@ import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider; import net.fabricmc.loom.configuration.providers.forge.PatchProvider; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; import net.fabricmc.loom.extension.LoomFiles; import net.fabricmc.loom.extension.MixinExtension; @@ -90,21 +92,31 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { JarProcessorManager getJarProcessorManager(); - default MinecraftProviderImpl getMinecraftProvider() { - return getDependencyManager().getProvider(MinecraftProviderImpl.class); + MinecraftProvider getMinecraftProvider(); + + void setMinecraftProvider(MinecraftProvider minecraftProvider); + + MappingsProviderImpl getMappingsProvider(); + + void setMappingsProvider(MappingsProviderImpl mappingsProvider); + + NamedMinecraftProvider getNamedMinecraftProvider(); + + IntermediaryMinecraftProvider getIntermediaryMinecraftProvider(); + + void setNamedMinecraftProvider(NamedMinecraftProvider namedMinecraftProvider); + + void setIntermediaryMinecraftProvider(IntermediaryMinecraftProvider intermediaryMinecraftProvider); + + default List getMinecraftJars(MappingsNamespace mappingsNamespace) { + return switch (mappingsNamespace) { + case NAMED -> getNamedMinecraftProvider().getMinecraftJars(); + case INTERMEDIARY -> getIntermediaryMinecraftProvider().getMinecraftJars(); + case OFFICIAL -> getMinecraftProvider().getMinecraftJars(); + }; } - default MappingsProviderImpl getMappingsProvider() { - return getDependencyManager().getProvider(isForge() ? FieldMigratedMappingsProvider.class : MappingsProviderImpl.class); - } - - default MinecraftMappedProvider getMinecraftMappedProvider() { - return getMappingsProvider().mappedProvider; - } - - File getMixinMappings(SourceSet sourceSet); - - FileCollection getAllMixinMappings(); + FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace); boolean isRootProject(); diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 0b0d7045..0e5d5546 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.function.Consumer; import org.gradle.api.Action; -import org.gradle.api.DomainObjectCollection; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.artifacts.Dependency; import org.gradle.api.file.ConfigurableFileCollection; @@ -39,13 +38,14 @@ import org.gradle.api.provider.Provider; import org.gradle.api.publish.maven.MavenPublication; import org.jetbrains.annotations.ApiStatus; -import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.api.decompilers.DecompilerOptions; import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; import net.fabricmc.loom.util.DeprecationHelper; import net.fabricmc.loom.util.ModPlatform; @@ -64,11 +64,9 @@ public interface LoomGradleExtensionAPI { getShareRemapCaches().set(true); } - DomainObjectCollection getGameDecompilers(); + NamedDomainObjectContainer getDecompilerOptions(); - default void addDecompiler(LoomDecompiler decompiler) { - getGameDecompilers().add(decompiler); - } + void decompilers(Action> action); ListProperty getGameJarProcessors(); @@ -150,6 +148,19 @@ public interface LoomGradleExtensionAPI { */ Property getEnableInterfaceInjection(); + @ApiStatus.Experimental + Property getMinecraftJarConfiguration(); + + @ApiStatus.Experimental + default void serverOnlyMinecraftJar() { + getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SERVER_ONLY); + } + + @ApiStatus.Experimental + default void splitMinecraftJar() { + getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SPLIT); + } + // =================== // Architectury Loom // =================== diff --git a/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java new file mode 100644 index 00000000..9cf58d6e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/decompilers/DecompilerOptions.java @@ -0,0 +1,81 @@ +/* + * 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.api.decompilers; + +import java.io.Serializable; +import java.util.Map; + +import com.google.common.base.Preconditions; +import org.gradle.api.Named; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; + +public abstract class DecompilerOptions implements Named { + /** + * Class name for to the {@link LoomDecompiler}. + */ + public abstract Property getDecompilerClassName(); + + /** + * Additional classpath entries for the decompiler jvm. + */ + public abstract ConfigurableFileCollection getClasspath(); + + /** + * Additional options to be passed to the decompiler. + */ + public abstract MapProperty getOptions(); + + /** + * Memory used for forked JVM in megabytes. + */ + public abstract Property getMemory(); + + /** + * Maximum number of threads the decompiler is allowed to use. + */ + public abstract Property getMaxThreads(); + + public DecompilerOptions() { + getDecompilerClassName().finalizeValueOnRead(); + getClasspath().finalizeValueOnRead(); + getOptions().finalizeValueOnRead(); + getMemory().convention(4096L).finalizeValueOnRead(); + getMaxThreads().convention(Runtime.getRuntime().availableProcessors()).finalizeValueOnRead(); + } + + // Done to work around weird issues with the workers, possibly https://github.com/gradle/gradle/issues/13422 + 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()); + return new Dto( + getDecompilerClassName().get(), + getOptions().get(), + getMaxThreads().get() + ); + } +} diff --git a/src/main/java/net/fabricmc/loom/api/decompilers/LoomDecompiler.java b/src/main/java/net/fabricmc/loom/api/decompilers/LoomDecompiler.java index d903fd96..c69a4337 100644 --- a/src/main/java/net/fabricmc/loom/api/decompilers/LoomDecompiler.java +++ b/src/main/java/net/fabricmc/loom/api/decompilers/LoomDecompiler.java @@ -26,13 +26,7 @@ package net.fabricmc.loom.api.decompilers; import java.nio.file.Path; -import org.gradle.api.Project; -import org.gradle.api.file.FileCollection; -import org.jetbrains.annotations.Nullable; - public interface LoomDecompiler { - String name(); - /** * @param sourcesDestination Decompiled sources jar * @param linemapDestination A byproduct of decompilation that lines up the compiled jar's line numbers with the decompiled @@ -41,12 +35,4 @@ public interface LoomDecompiler { * @param metaData Additional information that may or may not be needed while decompiling */ void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData); - - /** - * Add additional classpath entries to the decompiler classpath, can return a configuration. - */ - @Nullable - default FileCollection getBootstrapClasspath(Project project) { - return null; - } } 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 cbc7d3ab..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.MinecraftProvider; -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/api/mappings/layered/spec/LayeredMappingSpecBuilder.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java index 65bb3d67..9a6c55e8 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/spec/LayeredMappingSpecBuilder.java @@ -42,7 +42,7 @@ public interface LayeredMappingSpecBuilder { LayeredMappingSpecBuilder addLayer(MappingsSpec mappingSpec); /** - * Add a layer that uses the official mappings provided by Mojang with the default options. + * Add a layer that uses the official mappings provided by Mojang with the default decompilerOptions. */ default LayeredMappingSpecBuilder officialMojangMappings() { return officialMojangMappings(builder -> { }); 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 7ca55479..2a1e7917 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -43,6 +43,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; /** @@ -86,7 +87,7 @@ public abstract class AnnotationProcessorInvoker { Path mappings = loom.isForge() ? loom.getMappingsProvider().mixinTinyMappingsWithSrg : loom.getMappingsProvider().tinyMappings; Map args = new HashMap<>() {{ put(Constants.MixinArguments.IN_MAP_FILE_NAMED_INTERMEDIARY, mappings.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 685bd41c..0863eb7b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -24,7 +24,6 @@ package net.fabricmc.loom.configuration; -import java.io.File; import java.nio.charset.StandardCharsets; import org.gradle.api.NamedDomainObjectProvider; @@ -35,7 +34,6 @@ import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.AbstractCopyTask; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.javadoc.Javadoc; @@ -43,8 +41,10 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.build.mixin.JavaApInvoker; import net.fabricmc.loom.build.mixin.KaptApInvoker; import net.fabricmc.loom.build.mixin.ScalaApInvoker; -import net.fabricmc.loom.configuration.providers.LaunchProvider; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor; +import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor; +import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor; +import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.providers.forge.FieldMigratedMappingsProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeProvider; import net.fabricmc.loom.configuration.providers.forge.ForgeUniversalProvider; @@ -53,9 +53,11 @@ import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider; import net.fabricmc.loom.configuration.providers.forge.PatchProvider; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; import net.fabricmc.loom.extension.MixinExtension; -import net.fabricmc.loom.task.GenerateSourcesTask; -import net.fabricmc.loom.task.UnpickJarTask; import net.fabricmc.loom.util.Constants; public final class CompileConfiguration { @@ -179,6 +181,11 @@ public final class CompileConfiguration { extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, project); extendsFrom(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, project); + + // Add the dev time dependencies + project.getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR); + project.getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER); + project.getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS); } public static void configureCompile(Project p) { @@ -191,11 +198,15 @@ public final class CompileConfiguration { }); p.afterEvaluate(project -> { + try { + setupMinecraft(project); + } catch (Exception e) { + throw new RuntimeException("Failed to setup minecraft", e); + } + LoomDependencyManager dependencyManager = new LoomDependencyManager(); extension.setDependencyManager(dependencyManager); - dependencyManager.addProvider(new MinecraftProviderImpl(project)); - if (extension.isForge()) { dependencyManager.addProvider(new ForgeProvider(project)); dependencyManager.addProvider(new ForgeUserdevProvider(project)); @@ -211,9 +222,6 @@ public final class CompileConfiguration { dependencyManager.addProvider(new ForgeUniversalProvider(project)); } - dependencyManager.addProvider(extension.isForge() ? new FieldMigratedMappingsProvider(project) : new MappingsProviderImpl(project)); - dependencyManager.addProvider(new LaunchProvider(project)); - dependencyManager.handleDependencies(project); extension.getRemapArchives().finalizeValue(); @@ -246,6 +254,69 @@ public final class CompileConfiguration { } } + // This is not thread safe across projects synchronize it here just to be sure, might be possible to move this further down, but for now this will do. + private static synchronized void setupMinecraft(Project project) throws Exception { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + final MinecraftJarConfiguration jarConfiguration = extension.getMinecraftJarConfiguration().get(); + + // Provide the vanilla mc jars -- TODO share across projects. + final MinecraftProvider minecraftProvider = jarConfiguration.getMinecraftProviderFunction().apply(project); + extension.setMinecraftProvider(minecraftProvider); + minecraftProvider.provide(); + + final DependencyInfo mappingsDep = DependencyInfo.create(project, Constants.Configurations.MAPPINGS); + final MappingsProviderImpl mappingsProvider = MappingsProviderImpl.getInstance(project, mappingsDep, minecraftProvider); + extension.setMappingsProvider(mappingsProvider); + mappingsProvider.applyToProject(project, mappingsDep); + + // Provide the remapped mc jars + final IntermediaryMinecraftProvider intermediaryMinecraftProvider = jarConfiguration.getIntermediaryMinecraftProviderBiFunction().apply(project, minecraftProvider); + NamedMinecraftProvider namedMinecraftProvider = jarConfiguration.getNamedMinecraftProviderBiFunction().apply(project, minecraftProvider); + + final JarProcessorManager jarProcessorManager = createJarProcessorManager(project); + + if (jarProcessorManager.active()) { + // Wrap the named MC provider for one that will provide the processed jars + namedMinecraftProvider = jarConfiguration.getProcessedNamedMinecraftProviderBiFunction().apply(namedMinecraftProvider, jarProcessorManager); + } + + extension.setIntermediaryMinecraftProvider(intermediaryMinecraftProvider); + intermediaryMinecraftProvider.provide(true); + + extension.setNamedMinecraftProvider(namedMinecraftProvider); + namedMinecraftProvider.provide(true); + } + + private static JarProcessorManager createJarProcessorManager(Project project) { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + + if (extension.getAccessWidenerPath().isPresent()) { + extension.getGameJarProcessors().add(new AccessWidenerJarProcessor(project)); + } + + if (extension.getEnableTransitiveAccessWideners().get()) { + TransitiveAccessWidenerJarProcessor transitiveAccessWidenerJarProcessor = new TransitiveAccessWidenerJarProcessor(project); + + if (!transitiveAccessWidenerJarProcessor.isEmpty()) { + extension.getGameJarProcessors().add(transitiveAccessWidenerJarProcessor); + } + } + + if (extension.getEnableInterfaceInjection().get()) { + InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(project); + + if (!jarProcessor.isEmpty()) { + extension.getGameJarProcessors().add(jarProcessor); + } + } + + JarProcessorManager processorManager = new JarProcessorManager(extension.getGameJarProcessors().get()); + extension.setJarProcessorManager(processorManager); + processorManager.setupProcessors(); + + return processorManager; + } + private static void setupMixinAp(Project project, MixinExtension mixin) { mixin.init(); @@ -270,39 +341,10 @@ public final class CompileConfiguration { } private static void configureDecompileTasks(Project project) { - final TaskContainer tasks = project.getTasks(); final LoomGradleExtension extension = LoomGradleExtension.get(project); - MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); - - File mappedJar = mappingsProvider.mappedProvider.getMappedJar(); - - if (mappingsProvider.hasUnpickDefinitions()) { - File outputJar = mappingsProvider.mappedProvider.getUnpickedJar(); - - tasks.register("unpickJar", UnpickJarTask.class, unpickJarTask -> { - unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile()); - unpickJarTask.getInputJar().set(mappingsProvider.mappedProvider.getMappedJar()); - unpickJarTask.getOutputJar().set(outputJar); - }); - - mappedJar = outputJar; - } - - final File inputJar = mappedJar; - - extension.getGameDecompilers().configureEach(decompiler -> { - String taskName = "genSourcesWith" + decompiler.name(); - - // Set the input jar for the task after evaluation has occurred. - tasks.named(taskName, GenerateSourcesTask.class).configure(task -> { - task.getInputJar().set(inputJar); - - if (mappingsProvider.hasUnpickDefinitions()) { - task.dependsOn(tasks.named("unpickJar")); - } - }); - }); + extension.getMinecraftJarConfiguration().get().getDecompileConfigurationBiFunction() + .apply(project, extension.getNamedMinecraftProvider()).afterEvaluation(); } private static void extendsFrom(String a, String b, Project project) { diff --git a/src/main/java/net/fabricmc/loom/configuration/DependencyInfo.java b/src/main/java/net/fabricmc/loom/configuration/DependencyInfo.java new file mode 100644 index 00000000..3c09e2bf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/DependencyInfo.java @@ -0,0 +1,140 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration; + +import java.io.File; +import java.util.Optional; +import java.util.Set; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.DependencySet; +import org.gradle.api.artifacts.ResolvedDependency; +import org.gradle.api.artifacts.SelfResolvingDependency; + +public class DependencyInfo { + final Project project; + final Dependency dependency; + final Configuration sourceConfiguration; + + private String resolvedVersion = null; + + public static DependencyInfo create(Project project, String configuration) { + return create(project, project.getConfigurations().getByName(configuration)); + } + + public static DependencyInfo create(Project project, Configuration configuration) { + DependencySet dependencies = configuration.getDependencies(); + + if (dependencies.isEmpty()) { + throw new IllegalArgumentException(String.format("Configuration '%s' has no dependencies", configuration.getName())); + } + + if (dependencies.size() != 1) { + throw new IllegalArgumentException(String.format("Configuration '%s' must only have 1 dependency", configuration.getName())); + } + + return create(project, dependencies.iterator().next(), configuration); + } + + public static DependencyInfo create(Project project, Dependency dependency, Configuration sourceConfiguration) { + if (dependency instanceof SelfResolvingDependency selfResolvingDependency) { + return new FileDependencyInfo(project, selfResolvingDependency, sourceConfiguration); + } else { + return new DependencyInfo(project, dependency, sourceConfiguration); + } + } + + DependencyInfo(Project project, Dependency dependency, Configuration sourceConfiguration) { + this.project = project; + this.dependency = dependency; + this.sourceConfiguration = sourceConfiguration; + } + + public Dependency getDependency() { + return dependency; + } + + public String getResolvedVersion() { + if (resolvedVersion != null) { + return resolvedVersion; + } + + for (ResolvedDependency rd : sourceConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies()) { + if (rd.getModuleGroup().equals(dependency.getGroup()) && rd.getModuleName().equals(dependency.getName())) { + resolvedVersion = rd.getModuleVersion(); + return resolvedVersion; + } + } + + resolvedVersion = dependency.getVersion(); + return resolvedVersion; + } + + public Configuration getSourceConfiguration() { + return sourceConfiguration; + } + + public Set resolve() { + if (dependency instanceof SelfResolvingDependency selfResolvingDependency) { + return selfResolvingDependency.resolve(); + } + + return sourceConfiguration.files(dependency); + } + + public Optional resolveFile() { + Set files = resolve(); + + if (files.isEmpty()) { + return Optional.empty(); + } else if (files.size() > 1) { + StringBuilder builder = new StringBuilder(this.toString()); + builder.append(" resolves to more than one file:"); + + for (File f : files) { + builder.append("\n\t-").append(f.getAbsolutePath()); + } + + throw new RuntimeException(builder.toString()); + } else { + return files.stream().findFirst(); + } + } + + @Override + public String toString() { + return getDepString(); + } + + public String getDepString() { + return dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion(); + } + + public String getResolvedDepString() { + return dependency.getGroup() + ":" + dependency.getName() + ":" + getResolvedVersion(); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java b/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java deleted file mode 100644 index c50102d7..00000000 --- a/src/main/java/net/fabricmc/loom/configuration/DependencyProvider.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2016-2021 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.configuration; - -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import com.google.common.collect.Iterables; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import org.apache.commons.io.FilenameUtils; -import org.gradle.api.InvalidUserDataException; -import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ResolvedDependency; -import org.gradle.api.artifacts.SelfResolvingDependency; - -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.LoomGradlePlugin; -import net.fabricmc.loom.configuration.providers.MinecraftProvider; -import net.fabricmc.loom.extension.LoomFiles; -import net.fabricmc.loom.util.ZipUtils; - -public abstract class DependencyProvider { - private LoomDependencyManager dependencyManager; - private final Project project; - private final LoomGradleExtension extension; - - public DependencyProvider(Project project) { - this.project = project; - this.extension = LoomGradleExtension.get(project); - } - - public abstract void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception; - - public abstract String getTargetConfig(); - - public Dependency addDependency(Object object, String target) { - if (object instanceof File) { - object = project.files(object); - } - - return project.getDependencies().add(target, object); - } - - public void register(LoomDependencyManager dependencyManager) { - this.dependencyManager = dependencyManager; - } - - public LoomDependencyManager getDependencyManager() { - return dependencyManager; - } - - public Project getProject() { - return project; - } - - public LoomGradleExtension getExtension() { - return extension; - } - - public LoomFiles getDirectories() { - return getExtension().getFiles(); - } - - public MinecraftProvider getMinecraftProvider() { - return getExtension().getMinecraftProvider(); - } - - public boolean isRefreshDeps() { - return LoomGradlePlugin.refreshDeps; - } - - public static class DependencyInfo { - final Project project; - final Dependency dependency; - final Configuration sourceConfiguration; - - private String resolvedVersion = null; - - public static DependencyInfo create(Project project, Dependency dependency, Configuration sourceConfiguration) { - if (dependency instanceof SelfResolvingDependency selfResolvingDependency) { - return new FileDependencyInfo(project, selfResolvingDependency, sourceConfiguration); - } else { - return new DependencyInfo(project, dependency, sourceConfiguration); - } - } - - private DependencyInfo(Project project, Dependency dependency, Configuration sourceConfiguration) { - this.project = project; - this.dependency = dependency; - this.sourceConfiguration = sourceConfiguration; - } - - public Dependency getDependency() { - return dependency; - } - - public String getResolvedVersion() { - if (resolvedVersion != null) { - return resolvedVersion; - } - - for (ResolvedDependency rd : sourceConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies()) { - if (rd.getModuleGroup().equals(dependency.getGroup()) && rd.getModuleName().equals(dependency.getName())) { - resolvedVersion = rd.getModuleVersion(); - return resolvedVersion; - } - } - - resolvedVersion = dependency.getVersion(); - return resolvedVersion; - } - - public Configuration getSourceConfiguration() { - return sourceConfiguration; - } - - public Set resolve() { - if (dependency instanceof SelfResolvingDependency selfResolvingDependency) { - return selfResolvingDependency.resolve(); - } - - return sourceConfiguration.files(dependency); - } - - public Optional resolveFile() { - Set files = resolve(); - - if (files.isEmpty()) { - return Optional.empty(); - } else if (files.size() > 1) { - StringBuilder builder = new StringBuilder(this.toString()); - builder.append(" resolves to more than one file:"); - - for (File f : files) { - builder.append("\n\t-").append(f.getAbsolutePath()); - } - - throw new RuntimeException(builder.toString()); - } else { - return files.stream().findFirst(); - } - } - - @Override - public String toString() { - return getDepString(); - } - - public String getDepString() { - return dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion(); - } - - public String getResolvedDepString() { - return dependency.getGroup() + ":" + dependency.getName() + ":" + getResolvedVersion(); - } - } - - public static class FileDependencyInfo extends DependencyInfo { - protected final Map classifierToFile = new HashMap<>(); - protected final Set resolvedFiles; - protected final String group, name, version; - - FileDependencyInfo(Project project, SelfResolvingDependency dependency, Configuration configuration) { - super(project, dependency, configuration); - - Set files = dependency.resolve(); - this.resolvedFiles = files; - switch (files.size()) { - case 0 -> //Don't think Gradle would ever let you do this - throw new IllegalStateException("Empty dependency for " + configuration.getName()); - case 1 -> //Single file dependency - classifierToFile.put("", Iterables.getOnlyElement(files)); - default -> { //File collection, try work out the classifiers - List sortedFiles = files.stream().sorted(Comparator.comparing(File::getName, Comparator.comparingInt(String::length))).collect(Collectors.toList()); - //First element in sortedFiles is the one with the shortest name, we presume all the others are different classifier types of this - File shortest = sortedFiles.remove(0); - String shortestName = FilenameUtils.removeExtension(shortest.getName()); //name.jar -> name - - for (File file : sortedFiles) { - if (!file.getName().startsWith(shortestName)) { - //If there is another file which doesn't start with the same name as the presumed classifier-less one we're out of our depth - throw new IllegalArgumentException("Unable to resolve classifiers for " + this + " (failed to sort " + files + ')'); - } - } - - //We appear to be right, therefore this is the normal dependency file we want - classifierToFile.put("", shortest); - int start = shortestName.length(); - - for (File file : sortedFiles) { - //Now we just have to work out what classifier type the other files are, this shouldn't even return an empty string - String classifier = FilenameUtils.removeExtension(file.getName()).substring(start); - - //The classifier could well be separated with a dash (thing name.jar and name-sources.jar), we don't want that leading dash - if (classifierToFile.put(classifier.charAt(0) == '-' ? classifier.substring(1) : classifier, file) != null) { - throw new InvalidUserDataException("Duplicate classifiers for " + this + " (\"" + file.getName().substring(start) + "\" in " + files + ')'); - } - } - } - } - - if (dependency.getGroup() != null && dependency.getVersion() != null) { - group = dependency.getGroup(); - name = dependency.getName(); - version = dependency.getVersion(); - } else { - group = "net.fabricmc.synthetic"; - File root = classifierToFile.get(""); //We've built the classifierToFile map, now to try find a name and version for our dependency - byte[] modJson; - - try { - if ("jar".equals(FilenameUtils.getExtension(root.getName())) && (modJson = ZipUtils.unpackNullable(root.toPath(), "fabric.mod.json")) != null) { - //It's a Fabric mod, see how much we can extract out - JsonObject json = new Gson().fromJson(new String(modJson, StandardCharsets.UTF_8), JsonObject.class); - - if (json == null || !json.has("id") || !json.has("version")) { - throw new IllegalArgumentException("Invalid Fabric mod jar: " + root + " (malformed json: " + json + ')'); - } - - if (json.has("name")) { //Go for the name field if it's got one - name = json.get("name").getAsString(); - } else { - name = json.get("id").getAsString(); - } - - version = json.get("version").getAsString(); - } else { - //Not a Fabric mod, just have to make something up - name = FilenameUtils.removeExtension(root.getName()); - version = "1.0"; - } - } catch (IOException e) { - throw new UncheckedIOException("Failed to read input file: " + root, e); - } - } - } - - @Override - public String getResolvedVersion() { - return version; - } - - @Override - public String getDepString() { - //Use our custom name and version with the dummy group rather than the null:unspecified:null it would otherwise return - return group + ':' + name + ':' + version; - } - - @Override - public String getResolvedDepString() { - return getDepString(); - } - - @Override - public Set resolve() { - return this.resolvedFiles; - } - } -} diff --git a/src/main/java/net/fabricmc/loom/configuration/FileDependencyInfo.java b/src/main/java/net/fabricmc/loom/configuration/FileDependencyInfo.java new file mode 100644 index 00000000..2f55bbe0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/FileDependencyInfo.java @@ -0,0 +1,149 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Iterables; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import org.apache.commons.io.FilenameUtils; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.SelfResolvingDependency; + +import net.fabricmc.loom.util.ZipUtils; + +public class FileDependencyInfo extends DependencyInfo { + protected final Map classifierToFile = new HashMap<>(); + protected final Set resolvedFiles; + protected final String group, name, version; + + FileDependencyInfo(Project project, SelfResolvingDependency dependency, Configuration configuration) { + super(project, dependency, configuration); + + Set files = dependency.resolve(); + this.resolvedFiles = files; + switch (files.size()) { + case 0 -> //Don't think Gradle would ever let you do this + throw new IllegalStateException("Empty dependency?"); + case 1 -> //Single file dependency + classifierToFile.put("", Iterables.getOnlyElement(files)); + default -> { //File collection, try work out the classifiers + List sortedFiles = files.stream().sorted(Comparator.comparing(File::getName, Comparator.comparingInt(String::length))).collect(Collectors.toList()); + //First element in sortedFiles is the one with the shortest name, we presume all the others are different classifier types of this + File shortest = sortedFiles.remove(0); + String shortestName = FilenameUtils.removeExtension(shortest.getName()); //name.jar -> name + + for (File file : sortedFiles) { + if (!file.getName().startsWith(shortestName)) { + //If there is another file which doesn't start with the same name as the presumed classifier-less one we're out of our depth + throw new IllegalArgumentException("Unable to resolve classifiers for " + this + " (failed to sort " + files + ')'); + } + } + + //We appear to be right, therefore this is the normal dependency file we want + classifierToFile.put("", shortest); + int start = shortestName.length(); + + for (File file : sortedFiles) { + //Now we just have to work out what classifier type the other files are, this shouldn't even return an empty string + String classifier = FilenameUtils.removeExtension(file.getName()).substring(start); + + //The classifier could well be separated with a dash (thing name.jar and name-sources.jar), we don't want that leading dash + if (classifierToFile.put(classifier.charAt(0) == '-' ? classifier.substring(1) : classifier, file) != null) { + throw new InvalidUserDataException("Duplicate classifiers for " + this + " (\"" + file.getName().substring(start) + "\" in " + files + ')'); + } + } + } + } + + if (dependency.getGroup() != null && dependency.getVersion() != null) { + group = dependency.getGroup(); + name = dependency.getName(); + version = dependency.getVersion(); + } else { + group = "net.fabricmc.synthetic"; + File root = classifierToFile.get(""); //We've built the classifierToFile map, now to try find a name and version for our dependency + byte[] modJson; + + try { + if ("jar".equals(FilenameUtils.getExtension(root.getName())) && (modJson = ZipUtils.unpackNullable(root.toPath(), "fabric.mod.json")) != null) { + //It's a Fabric mod, see how much we can extract out + JsonObject json = new Gson().fromJson(new String(modJson, StandardCharsets.UTF_8), JsonObject.class); + + if (json == null || !json.has("id") || !json.has("version")) { + throw new IllegalArgumentException("Invalid Fabric mod jar: " + root + " (malformed json: " + json + ')'); + } + + if (json.has("name")) { //Go for the name field if it's got one + name = json.get("name").getAsString(); + } else { + name = json.get("id").getAsString(); + } + + version = json.get("version").getAsString(); + } else { + //Not a Fabric mod, just have to make something up + name = FilenameUtils.removeExtension(root.getName()); + version = "1.0"; + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to read input file: " + root, e); + } + } + } + + @Override + public String getResolvedVersion() { + return version; + } + + @Override + public String getDepString() { + //Use our custom name and version with the dummy group rather than the null:unspecified:null it would otherwise return + return group + ':' + name + ':' + version; + } + + @Override + public String getResolvedDepString() { + return getDepString(); + } + + @Override + public Set resolve() { + return this.resolvedFiles; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java index 665394fa..292a6806 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomDependencyManager.java @@ -29,15 +29,12 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import com.google.gson.JsonObject; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.DependencySet; import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; @@ -45,106 +42,17 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.LoomRepositoryPlugin; import net.fabricmc.loom.build.ModCompileRemapper; -import net.fabricmc.loom.configuration.DependencyProvider.DependencyInfo; import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; -import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.SourceRemapper; import net.fabricmc.loom.util.ZipUtils; public class LoomDependencyManager { - private static class ProviderList { - private final String key; - private final List providers = new ArrayList<>(); - - ProviderList(String key) { - this.key = key; - } - } - - private final List dependencyProviderList = new ArrayList<>(); - - public T addProvider(T provider) { - if (dependencyProviderList.contains(provider)) { - throw new RuntimeException("Provider is already registered"); - } - - if (getProvider(provider.getClass()) != null) { - throw new RuntimeException("Provider of this type is already registered"); - } - - provider.register(this); - dependencyProviderList.add(provider); - return provider; - } - - public T getProvider(Class clazz) { - for (DependencyProvider provider : dependencyProviderList) { - if (provider.getClass() == clazz) { - return (T) provider; - } - } - - return null; - } - public void handleDependencies(Project project) { List afterTasks = new ArrayList<>(); - MappingsProviderImpl mappingsProvider = null; - project.getLogger().info(":setting up loom dependencies"); LoomGradleExtension extension = LoomGradleExtension.get(project); - Map providerListMap = new HashMap<>(); - List targetProviders = new ArrayList<>(); - - for (DependencyProvider provider : dependencyProviderList) { - providerListMap.computeIfAbsent(provider.getTargetConfig(), (k) -> { - ProviderList list = new ProviderList(k); - targetProviders.add(list); - return list; - }).providers.add(provider); - - if (provider instanceof MappingsProviderImpl) { - mappingsProvider = (MappingsProviderImpl) provider; - } - } - - if (mappingsProvider == null) { - throw new RuntimeException("Could not find MappingsProvider instance!"); - } - - for (ProviderList list : targetProviders) { - Configuration configuration = project.getConfigurations().getByName(list.key); - DependencySet dependencies = configuration.getDependencies(); - - if (dependencies.isEmpty()) { - throw new IllegalArgumentException(String.format("No '%s' dependency was specified!", list.key)); - } - - if (dependencies.size() > 1) { - throw new IllegalArgumentException(String.format("Only one '%s' dependency should be specified, but %d were!", - list.key, - dependencies.size()) - ); - } - - for (Dependency dependency : dependencies) { - for (DependencyProvider provider : list.providers) { - DependencyProvider.DependencyInfo info = DependencyInfo.create(project, dependency, configuration); - - try { - provider.provide(info, afterTasks::add); - } catch (Exception e) { - throw new RuntimeException("Failed to provide " + dependency.getGroup() + ":" + dependency.getName() + ":" + dependency.getVersion() + " : " + e.toString() + "\n\tEnsure minecraft is not open and try running with --refresh-dependencies. Use --stacktrace to see the full stacktrace.", e); - } - } - } - } - - SourceRemapper sourceRemapper = new SourceRemapper(project, true); - String platformSuffix = extension.isForge() ? "_forge" : ""; - String mappingsIdentifier = mappingsProvider.mappingsIdentifier() + platformSuffix; if (extension.getInstallerData() == null && !extension.isForge()) { //If we've not found the installer JSON we've probably skipped remapping Fabric loader, let's go looking @@ -168,15 +76,18 @@ public class LoomDependencyManager { } } - if (extension.getInstallerData() == null) { - project.getLogger().warn("fabric-installer.json not found in classpath!"); - } - } + SourceRemapper sourceRemapper = new SourceRemapper(project, true); + String platformSuffix = extension.isForge() ? "_forge" : ""; + String mappingsIdentifier = extension.getMappingsProvider().mappingsIdentifier() + platformSuffix; ModCompileRemapper.remapDependencies(project, mappingsIdentifier, extension, sourceRemapper); sourceRemapper.remapAll(); + if (extension.getInstallerData() == null) { + project.getLogger().warn("fabric-installer.json not found in classpath!"); + } + for (Runnable runnable : afterTasks) { runnable.run(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java index 8b4a2fcc..e1b23ea3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java @@ -30,7 +30,6 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import java.util.Arrays; import com.google.common.hash.Hashing; import org.gradle.api.Project; @@ -39,6 +38,7 @@ import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.ZipUtils; public class AccessWidenerJarProcessor implements JarProcessor { @@ -57,7 +57,7 @@ public class AccessWidenerJarProcessor implements JarProcessor { @Override public String getId() { - return "loom:access_widener"; + return "loom:access_widener:" + Checksum.toHex(inputHash); } @Override @@ -91,21 +91,4 @@ public class AccessWidenerJarProcessor implements JarProcessor { throw new UncheckedIOException("Failed to write aw jar hash", e); } } - - @Override - public boolean isInvalid(File file) { - byte[] hash; - - try { - hash = ZipUtils.unpackNullable(file.toPath(), HASH_FILENAME); - } catch (IOException e) { - return true; - } - - if (hash == null) { - return true; - } - - return !Arrays.equals(inputHash, hash); // TODO how do we know if the current jar as the correct access applied? save the hash of the input? - } } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java index 700d41a2..84791156 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/TransitiveAccessWidenerJarProcessor.java @@ -174,7 +174,10 @@ public class TransitiveAccessWidenerJarProcessor implements JarProcessor { TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, "intermediary", "named"); tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project)); - tinyRemapper.readClassPath(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath()); + + for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) { + tinyRemapper.readClassPath(minecraftJar); + } return tinyRemapper; } catch (IOException e) { @@ -182,12 +185,6 @@ public class TransitiveAccessWidenerJarProcessor implements JarProcessor { } } - @Override - public boolean isInvalid(File file) { - // The hash is handled by getId() - return false; - } - private static class TransitiveDetectorVisitor implements AccessWidenerVisitor { private boolean transitive = false; diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java new file mode 100644 index 00000000..17718e81 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java @@ -0,0 +1,59 @@ +/* + * 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.decompile; + +import java.io.File; + +import org.gradle.api.Project; +import org.gradle.api.tasks.TaskProvider; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; +import net.fabricmc.loom.task.UnpickJarTask; + +public abstract class DecompileConfiguration { + protected final Project project; + protected final T minecraftProvider; + protected final LoomGradleExtension extension; + protected final MappingsProviderImpl mappingsProvider; + + public DecompileConfiguration(Project project, T minecraftProvider) { + this.project = project; + this.minecraftProvider = minecraftProvider; + this.extension = LoomGradleExtension.get(project); + this.mappingsProvider = extension.getMappingsProvider(); + } + + public abstract void afterEvaluation(); + + protected final TaskProvider createUnpickJarTask(String name, File inputJar, File outputJar) { + return project.getTasks().register(name, UnpickJarTask.class, unpickJarTask -> { + unpickJarTask.getUnpickDefinitions().set(mappingsProvider.getUnpickDefinitionsFile()); + unpickJarTask.getInputJar().set(inputJar); + unpickJarTask.getOutputJar().set(outputJar); + }); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java new file mode 100644 index 00000000..ccaf627d --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java @@ -0,0 +1,86 @@ +/* + * 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.decompile; + +import java.io.File; +import java.nio.file.Path; +import java.util.List; + +import org.gradle.api.Project; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; +import net.fabricmc.loom.task.GenerateSourcesTask; +import net.fabricmc.loom.util.Constants; + +public class SingleJarDecompileConfiguration extends DecompileConfiguration { + public SingleJarDecompileConfiguration(Project project, MappedMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + public final void afterEvaluation() { + List minecraftJars = minecraftProvider.getMinecraftJars(); + assert minecraftJars.size() == 1; + + final File namedJar = minecraftJars.get(0).toFile(); + + File mappedJar = namedJar; + + if (mappingsProvider.hasUnpickDefinitions()) { + File outputJar = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar"); + createUnpickJarTask("unpickJar", namedJar, outputJar); + + mappedJar = outputJar; + } + + final File inputJar = mappedJar; + + LoomGradleExtension.get(project).getDecompilerOptions().forEach(options -> { + final String decompilerName = options.getName().substring(0, 1).toUpperCase() + options.getName().substring(1); + String taskName = "genSourcesWith" + decompilerName; + // Decompiler will be passed to the constructor of GenerateSourcesTask + project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> { + task.getInputJar().set(inputJar); + task.getRuntimeJar().set(namedJar); + + task.dependsOn(project.getTasks().named("validateAccessWidener")); + task.setDescription("Decompile minecraft using %s.".formatted(decompilerName)); + task.setGroup(Constants.TaskGroup.FABRIC); + + if (mappingsProvider.hasUnpickDefinitions()) { + task.dependsOn(project.getTasks().named("unpickJar")); + } + }); + }); + + project.getTasks().register("genSources", task -> { + task.setDescription("Decompile minecraft using the default decompiler."); + task.setGroup(Constants.TaskGroup.FABRIC); + + task.dependsOn(project.getTasks().named("genSourcesWithCfr")); + }); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java new file mode 100644 index 00000000..60cd7932 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SplitDecompileConfiguration.java @@ -0,0 +1,116 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.decompile; + +import java.io.File; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.tasks.TaskProvider; + +import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; +import net.fabricmc.loom.task.GenerateSourcesTask; +import net.fabricmc.loom.task.UnpickJarTask; +import net.fabricmc.loom.util.Constants; + +public final class SplitDecompileConfiguration extends DecompileConfiguration { + public SplitDecompileConfiguration(Project project, MappedMinecraftProvider.Split minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + public void afterEvaluation() { + File commonJarToDecompile = minecraftProvider.getCommonJar().toFile(); + File clientOnlyJarToDecompile = minecraftProvider.getClientOnlyJar().toFile(); + + TaskProvider unpickCommonJar = null; + TaskProvider unpickClientOnlyJar = null; + + if (mappingsProvider.hasUnpickDefinitions()) { + commonJarToDecompile = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar"); + clientOnlyJarToDecompile = new File(extension.getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar"); + + unpickCommonJar = createUnpickJarTask("unpickCommonJar", minecraftProvider.getCommonJar().toFile(), commonJarToDecompile); + unpickClientOnlyJar = createUnpickJarTask("unpickClientOnlyJar", minecraftProvider.getClientOnlyJar().toFile(), clientOnlyJarToDecompile); + } + + // Need to re-declare them as final to access them from the lambada + final File commonJar = commonJarToDecompile; + final File clientOnlyJar = clientOnlyJarToDecompile; + final TaskProvider unpickCommonJarTask = unpickCommonJar; + final TaskProvider unpickClientOnlyJarTask = unpickClientOnlyJar; + + final TaskProvider commonDecompileTask = createDecompileTasks("Common", task -> { + task.getInputJar().set(commonJar); + task.getRuntimeJar().set(minecraftProvider.getCommonJar().toFile()); + + if (unpickCommonJarTask != null) { + task.dependsOn(unpickCommonJarTask); + } + }); + + final TaskProvider clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> { + task.getInputJar().set(clientOnlyJar); + task.getRuntimeJar().set(minecraftProvider.getClientOnlyJar().toFile()); + + if (unpickCommonJarTask != null) { + task.dependsOn(unpickClientOnlyJarTask); + } + + // Don't allow them to run at the same time. + task.mustRunAfter(commonDecompileTask); + }); + + project.getTasks().register("genSources", task -> { + task.setDescription("Decompile minecraft using the default decompiler."); + task.setGroup(Constants.TaskGroup.FABRIC); + + task.dependsOn(commonDecompileTask); + task.dependsOn(clientOnlyDecompileTask); + }); + } + + private TaskProvider createDecompileTasks(String name, Action configureAction) { + extension.getDecompilerOptions().forEach(options -> { + final String decompilerName = options.getName().substring(0, 1).toUpperCase() + options.getName().substring(1); + final String taskName = "gen%sSourcesWith%s".formatted(name, decompilerName); + + project.getTasks().register(taskName, GenerateSourcesTask.class, options).configure(task -> { + configureAction.execute(task); + task.dependsOn(project.getTasks().named("validateAccessWidener")); + task.setDescription("Decompile minecraft using %s.".formatted(decompilerName)); + task.setGroup(Constants.TaskGroup.FABRIC); + }); + }); + + return project.getTasks().register("gen%sSources".formatted(name), task -> { + task.setDescription("Decompile minecraft (%s) using the default decompiler.".formatted(name)); + task.setGroup(Constants.TaskGroup.FABRIC); + + task.dependsOn(project.getTasks().named("gen%sSourcesWithCfr".formatted(name))); + }); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaConfiguration.java index a8055d69..0933c8e9 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaConfiguration.java @@ -30,10 +30,12 @@ import org.gradle.api.Project; import org.gradle.api.tasks.TaskProvider; import org.gradle.execution.taskgraph.TaskExecutionGraphInternal; +import net.fabricmc.loom.task.LoomTasks; + public class IdeaConfiguration { public static void setup(Project project) { - TaskProvider ideaSyncTask = project.getTasks().register("ideaSyncTask", IdeaSyncTask.class, ideaSyncTask1 -> { - ideaSyncTask1.dependsOn(project.getTasks().named("downloadAssets")); + TaskProvider ideaSyncTask = project.getTasks().register("ideaSyncTask", IdeaSyncTask.class, task -> { + task.dependsOn(LoomTasks.getIDELaunchConfigureTaskName(project)); }); if (!IdeaUtils.isIdeaSync()) { diff --git a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java index 3350bbd8..4dbca195 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; @@ -38,6 +37,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import com.google.common.base.Preconditions; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import com.google.gson.Gson; @@ -52,8 +52,10 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.commons.Remapper; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.processors.JarProcessor; +import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Pair; import net.fabricmc.loom.util.TinyRemapperHelper; @@ -82,6 +84,12 @@ public class InterfaceInjectionProcessor implements JarProcessor { return injectedInterfaces.isEmpty(); } + @Override + public String getId() { + Preconditions.checkArgument(!isEmpty()); + return "loom:interface_injection:" + Checksum.toHex(inputHash); + } + @Override public void setup() { } @@ -147,23 +155,6 @@ public class InterfaceInjectionProcessor implements JarProcessor { }; } - @Override - public boolean isInvalid(File file) { - byte[] hash; - - try { - hash = ZipUtils.unpackNullable(file.toPath(), HASH_FILENAME); - } catch (IOException e) { - return true; - } - - if (hash == null) { - return true; - } - - return !Arrays.equals(inputHash, hash); - } - private List getInjectedInterfaces() { List result = new ArrayList<>(); @@ -272,7 +263,10 @@ public class InterfaceInjectionProcessor implements JarProcessor { try { TinyRemapper tinyRemapper = TinyRemapperHelper.getTinyRemapper(project, "intermediary", "named"); tinyRemapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project)); - tinyRemapper.readClassPath(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath()); + + for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) { + tinyRemapper.readClassPath(minecraftJar); + } return tinyRemapper; } catch (IOException e) { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index d24438e4..c3b32c2f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -58,7 +58,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; +import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.LoggerFilter; @@ -149,12 +149,11 @@ public class ModProcessor { private void remapJars(List remapList) throws IOException { final LoomGradleExtension extension = LoomGradleExtension.get(project); - final MinecraftMappedProvider mappedProvider = extension.getMinecraftMappedProvider(); final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); + final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm"); String fromM = extension.isForge() ? MappingsNamespace.SRG.toString() : MappingsNamespace.INTERMEDIARY.toString(); String toM = MappingsNamespace.NAMED.toString(); - Path intermediaryJar = extension.isForge() ? mappedProvider.getSrgJar().toPath() : mappedProvider.getIntermediaryJar().toPath(); Path[] mcDeps = project.getConfigurations().getByName(Constants.Configurations.LOADER_DEPENDENCIES).getFiles() .stream().map(File::toPath).toArray(Path[]::new); @@ -163,12 +162,21 @@ public class ModProcessor { MemoryMappingTree mappings = (fromM.equals("srg") || toM.equals("srg")) && extension.isForge() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings(); LoggerFilter.replaceSystemOut(); - final TinyRemapper remapper = TinyRemapper.newRemapper() + TinyRemapper.Builder builder = TinyRemapper.newRemapper() .logger(project.getLogger()::lifecycle) .logUnknownInvokeDynamic(false) .withMappings(TinyRemapperHelper.create(mappings, fromM, toM, false)) - .renameInvalidLocals(false) - .build(); + .renameInvalidLocals(false); + + if (useKotlinExtension) { + builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE); + } + + final TinyRemapper remapper = builder.build(); + + for (Path minecraftJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) { + remapper.readClassPathAsync(minecraftJar); + } remapper.readClassPathAsync(intermediaryJar); diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessor.java index 71f9a6bf..b1cf4299 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessor.java @@ -33,11 +33,9 @@ public interface JarProcessor { *

If the jar processor implementation class supports creating multiple jar processors with different effects, * the needed configuration should also be included in this ID. Example: {@code path.to.MyJarProcessor#someOption}. * - * @return the ID of this jar processor + * @return the unique ID of this jar processor */ - default String getId() { - return getClass().getName(); - } + String getId(); void setup(); @@ -45,9 +43,4 @@ public interface JarProcessor { * Currently this is a destructive process that replaces the existing jar. */ void process(File file); - - /** - * Return true to make all jar processors run again, return false to use the existing results of jar processing. - */ - boolean isInvalid(File file); } diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java index 580dfefa..1d602c30 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/JarProcessorManager.java @@ -83,7 +83,7 @@ public class JarProcessorManager { throw new UncheckedIOException("Could not check jar manifest of " + file, e); } - return jarProcessors.stream().anyMatch(jarProcessor -> jarProcessor.isInvalid(file)); + return false; } private String getJarProcessorHash() { diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java deleted file mode 100644 index 744bc53e..00000000 --- a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftProcessedProvider.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2020-2021 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.configuration.processors; - -import java.io.File; -import java.io.IOException; -import java.util.function.Consumer; - -import org.apache.commons.io.FileUtils; -import org.gradle.api.Project; - -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; -import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; -import net.fabricmc.loom.util.Constants; - -public class MinecraftProcessedProvider extends MinecraftMappedProvider { - public final String projectMappedClassifier; - - private File projectMappedJar; - - private final JarProcessorManager jarProcessorManager; - - public MinecraftProcessedProvider(Project project, JarProcessorManager jarProcessorManager) { - super(project); - this.jarProcessorManager = jarProcessorManager; - this.projectMappedClassifier = "project-" + project.getPath().replace(':', '@') - + "-mapped"; - } - - @Override - protected void addDependencies(DependencyInfo dependency, Consumer postPopulationScheduler) { - boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty(); - - if (jarProcessorManager.isInvalid(projectMappedJar) || isRefreshDeps() || isForgeAtDirty) { - getProject().getLogger().info(":processing mapped jar"); - invalidateJar(); - - try { - FileUtils.copyFile(super.getMappedJar(), projectMappedJar); - } catch (IOException e) { - throw new RuntimeException("Failed to copy source jar", e); - } - - jarProcessorManager.process(projectMappedJar); - } - - getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED, - getProject().getDependencies().module("net.minecraft:" + minecraftProvider.getJarPrefix() + "minecraft-" + projectMappedClassifier + ":" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier())); - } - - private void invalidateJar() { - if (projectMappedJar.exists()) { - getProject().getLogger().warn("Invalidating project jar"); - - try { - FileUtils.forceDelete(projectMappedJar); - } catch (IOException e) { - throw new RuntimeException("Failed to invalidate jar, try stopping gradle daemon or closing the game", e); - } - } - } - - @Override - public void initFiles(MinecraftProviderImpl minecraftProvider, MappingsProviderImpl mappingsProvider) { - super.initFiles(minecraftProvider, mappingsProvider); - - projectMappedJar = new File(getDirectories().getRootProjectPersistentCache(), getMinecraftProvider().minecraftVersion() + "/" - + getExtension().getMappingsProvider().mappingsIdentifier() + "/" + minecraftProvider.getJarPrefix() + "minecraft-" + projectMappedClassifier + ".jar"); - } - - @Override - public File getMappedJar() { - return projectMappedJar; - } -} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java deleted file mode 100644 index 028f4d52..00000000 --- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2019-2021 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.configuration.providers; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringJoiner; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import org.apache.commons.io.FileUtils; -import org.gradle.api.Project; -import org.gradle.api.logging.configuration.ConsoleOutput; -import org.gradle.api.plugins.JavaPlugin; - -import net.fabricmc.loom.configuration.DependencyProvider; -import net.fabricmc.loom.configuration.RemappedConfigurationEntry; -import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; -import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.PropertyUtil; - -public class LaunchProvider extends DependencyProvider { - public LaunchProvider(Project project) { - super(project); - } - - @Override - public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws IOException { - final String nativesPath = getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath(); - - final LaunchConfig launchConfig = new LaunchConfig() - .property("fabric.development", "true") - .property("fabric.remapClasspathFile", getRemapClasspathFile().getAbsolutePath()) - .property("log4j.configurationFile", getAllLog4JConfigFiles()) - .property("log4j2.formatMsgNoLookups", "true") - - .property("client", "java.library.path", nativesPath) - .property("client", "org.lwjgl.librarypath", nativesPath); - - if (!getExtension().isForge()) { - launchConfig - .argument("client", "--assetIndex") - .argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion())) - .argument("client", "--assetsDir") - .argument("client", new File(getDirectories().getUserCache(), "assets").getAbsolutePath()); - } - - if (getExtension().isForge()) { - launchConfig - // Should match YarnNamingService.PATH_TO_MAPPINGS in forge-runtime - .property("fabric.yarnWithSrg.path", getExtension().getMappingsProvider().tinyMappingsWithSrg.toAbsolutePath().toString()) - - .argument("data", "--all") - .argument("data", "--mod") - .argument("data", String.join(",", getExtension().getForge().getDataGenMods())) - .argument("data", "--output") - .argument("data", getProject().file("src/generated/resources").getAbsolutePath()) - - .property("mixin.env.remapRefMap", "true"); - - if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) { - launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingsProvider().mixinTinyMappingsWithSrg.toAbsolutePath().toString()); - } else { - launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.toAbsolutePath().toString()); - } - - Set mixinConfigs = PropertyUtil.getAndFinalize(getExtension().getForge().getMixinConfigs()); - - if (!mixinConfigs.isEmpty()) { - for (String config : mixinConfigs) { - launchConfig.argument("-mixin.config"); - launchConfig.argument(config); - } - } - } - - addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); - addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); - addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); - - if (getExtension().isForge()) { - addDependency(Constants.Dependencies.FORGE_RUNTIME + Constants.Dependencies.Versions.FORGE_RUNTIME, Constants.Configurations.FORGE_EXTRA); - addDependency(Constants.Dependencies.JAVAX_ANNOTATIONS + Constants.Dependencies.Versions.JAVAX_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); - } - - for (LaunchProviderSettings settings : getExtension().getLaunchConfigs()) { - settings.evaluateNow(); - - for (String argument : settings.getArguments()) { - launchConfig.argument(settings.getName(), argument); - } - - for (Map.Entry property : settings.getProperties()) { - launchConfig.property(settings.getName(), property.getKey(), property.getValue()); - } - } - - final boolean plainConsole = getProject().getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain; - final boolean ansiSupportedIDE = new File(getProject().getRootDir(), ".vscode").exists() - || new File(getProject().getRootDir(), ".idea").exists() - || (Arrays.stream(getProject().getRootDir().listFiles()).anyMatch(file -> file.getName().endsWith(".iws"))); - - //Enable ansi by default for idea and vscode when gradle is not ran with plain console. - if (ansiSupportedIDE && !plainConsole) { - launchConfig.property("fabric.log.disableAnsi", "false"); - } - - writeLog4jConfig(); - FileUtils.writeStringToFile(getDirectories().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8); - - postPopulationScheduler.accept(this::writeRemapClassPath); - } - - private File getLog4jConfigFile() { - return getDirectories().getDefaultLog4jConfigFile(); - } - - private String getAllLog4JConfigFiles() { - return getExtension().getLog4jConfigs().getFiles().stream() - .map(File::getAbsolutePath) - .collect(Collectors.joining(",")); - } - - private File getRemapClasspathFile() { - return new File(getDirectories().getDevLauncherConfig().getParentFile(), "remapClasspath.txt"); - } - - private void writeLog4jConfig() { - try (InputStream is = LaunchProvider.class.getClassLoader().getResourceAsStream("log4j2.fabric.xml")) { - Files.deleteIfExists(getLog4jConfigFile().toPath()); - Files.copy(is, getLog4jConfigFile().toPath()); - } catch (IOException e) { - throw new RuntimeException("Failed to generate log4j config", e); - } - } - - private void writeRemapClassPath() { - List inputConfigurations = new ArrayList<>(); - inputConfigurations.add(Constants.Configurations.LOADER_DEPENDENCIES); - inputConfigurations.addAll(Constants.MOD_COMPILE_ENTRIES.stream().map(RemappedConfigurationEntry::sourceConfiguration).collect(Collectors.toList())); - - List remapClasspath = new ArrayList<>(); - - for (String inputConfiguration : inputConfigurations) { - remapClasspath.addAll(getProject().getConfigurations().getByName(inputConfiguration).getFiles()); - } - - remapClasspath.add(getExtension().getMinecraftMappedProvider().getIntermediaryJar()); - - if (getExtension().isForgeAndNotOfficial()) { - remapClasspath.add(getExtension().getMinecraftMappedProvider().getForgeIntermediaryJar()); - } - - String str = remapClasspath.stream() - .map(File::getAbsolutePath) - .collect(Collectors.joining(File.pathSeparator)); - - try { - Files.writeString(getRemapClasspathFile().toPath(), str); - } catch (IOException e) { - throw new RuntimeException("Failed to generate remap classpath", e); - } - } - - @Override - public String getTargetConfig() { - return Constants.Configurations.MINECRAFT_NAMED; - } - - public static class LaunchConfig { - private final Map> values = new HashMap<>(); - - public LaunchConfig property(String key, String value) { - return property("common", key, value); - } - - public LaunchConfig property(String side, String key, String value) { - values.computeIfAbsent(side + "Properties", (s -> new ArrayList<>())) - .add(String.format("%s=%s", key, value)); - return this; - } - - public LaunchConfig argument(String value) { - return argument("common", value); - } - - public LaunchConfig argument(String side, String value) { - values.computeIfAbsent(side + "Args", (s -> new ArrayList<>())) - .add(value); - return this; - } - - public String asString() { - StringJoiner stringJoiner = new StringJoiner("\n"); - - for (Map.Entry> entry : values.entrySet()) { - stringJoiner.add(entry.getKey()); - - for (String s : entry.getValue()) { - stringJoiner.add("\t" + s); - } - } - - return stringJoiner.toString(); - } - } -} 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 0ee89176..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; @@ -35,7 +36,8 @@ 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.MinecraftProvider; +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 ce274c94..9b2b78b6 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 @@ -29,146 +29,134 @@ import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.UncheckedIOException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.Consumer; -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.gradle.api.artifacts.Configuration; 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.DependencyProvider; -import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor; -import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor; -import net.fabricmc.loom.configuration.ifaceinject.InterfaceInjectionProcessor; -import net.fabricmc.loom.configuration.processors.JarProcessorManager; -import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; -import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; -import net.fabricmc.loom.configuration.providers.forge.SrgProvider; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; +import net.fabricmc.loom.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.srg.MCPReader; -import net.fabricmc.loom.util.srg.SrgMerger; -import net.fabricmc.loom.util.srg.SrgNamedWriter; +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; -import net.fabricmc.stitch.commands.tinyv2.TinyFile; -import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer; -public class MappingsProviderImpl extends DependencyProvider implements MappingsProvider { - public MinecraftMappedProvider mappedProvider; - public MinecraftPatchedProvider patchedProvider; +public class MappingsProviderImpl implements MappingsProvider, SharedService { + private static final Logger LOGGER = LoggerFactory.getLogger(MappingsProviderImpl.class); - public String mappingsIdentifier; + private Supplier mappingTree; + public final String mappingsIdentifier; - private Path mappingsWorkingDir; - private Path intermediaryTiny; - private boolean hasRefreshed = false; + 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; - public Path tinyMappingsWithSrg; - public Path mixinTinyMappingsWithSrg; // FORGE: The mixin mappings have srg names in intermediary. - public Path srgToNamedSrg; // FORGE: srg to named in srg file format - private Path unpickDefinitions; + public final Path tinyMappings; + public final Path tinyMappingsJar; + private final Path unpickDefinitions; + private boolean hasUnpickDefinitions; private UnpickMetadata unpickMetadata; - private MemoryMappingTree mappingTree; - private MemoryMappingTree mappingTreeWithSrg; private Map signatureFixes; - public MappingsProviderImpl(Project project) { - super(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 MemoryMappingTree getMappingsWithSrg() throws IOException { - return Objects.requireNonNull(mappingTreeWithSrg, "Cannot get mappings before they have been read"); + 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"); + + 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())); + } + }); + + final String mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, jarInfo.v2()), minecraftProvider.minecraftVersion()); + final Path workingDir = minecraftProvider.dir(mappingsIdentifier).toPath(); + + var mappingProvider = new MappingsProviderImpl(mappingsIdentifier, workingDir, intermediaryService); + + try { + mappingProvider.setup(minecraftProvider, inputJar); + } catch (IOException e) { + cleanWorkingDirectory(workingDir); + throw new UncheckedIOException("Failed to setup mappings: " + dependency.getDepString(), e); + } + + return mappingProvider; } - @Override - public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { - MinecraftProviderImpl minecraftProvider = getDependencyManager().getProvider(MinecraftProviderImpl.class); - - getProject().getLogger().info(":setting up mappings (" + dependency.getDependency().getName() + " " + dependency.getResolvedVersion() + ")"); - - String version = dependency.getResolvedVersion(); - File mappingsJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not find yarn mappings: " + dependency)); - - String mappingsName = StringUtils.removeSuffix(dependency.getDependency().getGroup() + "." + dependency.getDependency().getName(), "-unmerged"); - boolean isV2 = isV2(dependency, mappingsJar); - this.mappingsIdentifier = createMappingsIdentifier(mappingsName, version, getMappingsClassifier(dependency, isV2)); - - initFiles(); + private void setup(MinecraftProvider minecraftProvider, Path inputJar) throws IOException { + if (isRefreshDeps()) { + cleanWorkingDirectory(mappingsWorkingDir); + } if (Files.notExists(tinyMappings) || isRefreshDeps()) { - storeMappings(getProject(), minecraftProvider, mappingsJar.toPath(), postPopulationScheduler); + storeMappings(minecraftProvider, inputJar); } else { - try (FileSystem fileSystem = FileSystems.newFileSystem(mappingsJar.toPath(), (ClassLoader) null)) { + try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) { extractExtras(fileSystem); } } - if (getExtension().isForge()) { - patchedProvider = new MinecraftPatchedProvider(getProject()); - patchedProvider.provide(dependency, postPopulationScheduler); - } - - mappingTree = readMappings(tinyMappings); - manipulateMappings(mappingsJar.toPath()); - - if (getExtension().shouldGenerateSrgTiny()) { - if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) { - // Merge tiny mappings with srg - SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true); - } - - mappingTreeWithSrg = readMappings(tinyMappingsWithSrg); - } - if (Files.notExists(tinyMappingsJar) || isRefreshDeps()) { Files.deleteIfExists(tinyMappingsJar); 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(), @@ -176,95 +164,14 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings dependency.getDependency().getVersion() ); - getProject().getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation); - populateUnpickClasspath(); + project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation); + populateUnpickClasspath(project); } - if (getExtension().isForge()) { - if (!getExtension().shouldGenerateSrgTiny()) { - throw new IllegalStateException("We have to generate srg tiny in a forge environment!"); - } - - if (Files.notExists(mixinTinyMappingsWithSrg) || isRefreshDeps()) { - List lines = new ArrayList<>(Files.readAllLines(tinyMappingsWithSrg)); - lines.set(0, lines.get(0).replace("intermediary", "yraidemretni").replace("srg", "intermediary")); - Files.deleteIfExists(mixinTinyMappingsWithSrg); - Files.write(mixinTinyMappingsWithSrg, lines); - } - - if (Files.notExists(srgToNamedSrg) || isRefreshDeps()) { - SrgNamedWriter.writeTo(getProject().getLogger(), srgToNamedSrg, getMappingsWithSrg(), "srg", "named"); - } - } - - addDependency(getProject().getDependencies().module("loom.resolved:mappings:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier()), Constants.Configurations.MAPPINGS_FINAL); - - LoomGradleExtension extension = getExtension(); - - if (extension.getAccessWidenerPath().isPresent()) { - extension.getGameJarProcessors().add(new AccessWidenerJarProcessor(getProject())); - } - - if (extension.getEnableTransitiveAccessWideners().get()) { - TransitiveAccessWidenerJarProcessor transitiveAccessWidenerJarProcessor = new TransitiveAccessWidenerJarProcessor(getProject()); - - if (!transitiveAccessWidenerJarProcessor.isEmpty()) { - extension.getGameJarProcessors().add(transitiveAccessWidenerJarProcessor); - } - } - - if (extension.getEnableInterfaceInjection().get()) { - InterfaceInjectionProcessor jarProcessor = new InterfaceInjectionProcessor(getProject()); - - if (!jarProcessor.isEmpty()) { - extension.getGameJarProcessors().add(jarProcessor); - } - } - - extension.getAccessWidenerPath().finalizeValue(); - extension.getGameJarProcessors().finalizeValue(); - JarProcessorManager processorManager = new JarProcessorManager(extension.getGameJarProcessors().get()); - extension.setJarProcessorManager(processorManager); - processorManager.setupProcessors(); - - if (extension.isForge()) { - patchedProvider.finishProvide(); - } - - if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) { - mappedProvider = new MinecraftProcessedProvider(getProject(), processorManager); - getProject().getLogger().info("Using project based jar storage"); - } else { - mappedProvider = new MinecraftMappedProvider(getProject()); - } - - mappedProvider.initFiles(minecraftProvider, this); - mappedProvider.provide(dependency, postPopulationScheduler); + project.getDependencies().add(Constants.Configurations.MAPPINGS_FINAL, project.files(tinyMappingsJar.toFile())); } - protected Path getRawSrgFile() throws IOException { - LoomGradleExtension extension = getExtension(); - - if (extension.getSrgProvider().isTsrgV2()) { - return extension.getSrgProvider().getMergedMojangTrimmed(); - } - - return extension.getSrgProvider().getSrg(); - } - - public Path getMojmapSrgFileIfPossible() { - try { - LoomGradleExtension extension = getExtension(); - return SrgProvider.getMojmapTsrg2(getProject(), extension); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void manipulateMappings(Path mappingsJar) throws IOException { - } - - private String getMappingsClassifier(DependencyInfo dependency, boolean isV2) { + private static String getMappingsClassifier(DependencyInfo dependency, boolean isV2) { String[] depStringSplit = dependency.getDepString().split(":"); if (depStringSplit.length >= 4) { @@ -274,122 +181,46 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings return isV2 ? "-v2" : ""; } - private boolean isV2(DependencyInfo dependency, File mappingsJar) throws IOException { - String minecraftVersion = getMinecraftProvider().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)) { - getProject().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") || mappingsJar.getName().endsWith("-mergedv2.jar"); - } else { - return doesJarContainV2Mappings(mappingsJar.toPath()); - } - } - - private void storeMappings(Project project, MinecraftProviderImpl minecraftProvider, Path yarnJar, Consumer postPopulationScheduler) - throws IOException { - project.getLogger().info(":extracting " + yarnJar.getFileName()); - - if (isMCP(yarnJar)) { - try { - readAndMergeMCP(yarnJar, postPopulationScheduler); - } catch (Exception e) { - throw new RuntimeException(e); - } - - return; - } - - try (FileSystem fileSystem = FileSystems.newFileSystem(yarnJar, (ClassLoader) null)) { + try (FileSystem fileSystem = FileSystems.newFileSystem(inputJar, (ClassLoader) null)) { extractMappings(fileSystem, baseTinyMappings); extractExtras(fileSystem); } - if (areMappingsMergedV2(baseTinyMappings)) { - // Architectury Loom Patch - // If a merged tiny v2 mappings file is provided - // Skip merging, should save a lot of time - Files.copy(baseTinyMappings, tinyMappings, StandardCopyOption.REPLACE_EXISTING); - } else if (areMappingsV2(baseTinyMappings)) { + if (areMappingsV2(baseTinyMappings)) { // These are unmerged v2 mappings - mergeAndSaveMappings(project, baseTinyMappings, tinyMappings); + MappingsMerger.mergeAndSaveMappings(baseTinyMappings, tinyMappings, intermediaryService.get()); } else { - // These are merged v1 mappings - Files.deleteIfExists(tinyMappings); - project.getLogger().lifecycle(":populating field names"); - suggestFieldNames(minecraftProvider, baseTinyMappings, tinyMappings); - } - } - - private static MemoryMappingTree readMappings(Path file) throws IOException { - MemoryMappingTree mappingTree = new MemoryMappingTree(); - MappingReader.read(file, mappingTree); - return mappingTree; - } - - private void readAndMergeMCP(Path mcpJar, Consumer postPopulationScheduler) throws Exception { - Path intermediaryTinyPath = getIntermediaryTiny(); - SrgProvider provider = getExtension().getSrgProvider(); - - if (provider == null) { - if (!getExtension().shouldGenerateSrgTiny()) { - Configuration srg = getProject().getConfigurations().maybeCreate(Constants.Configurations.SRG); - srg.setTransitive(false); + if (minecraftProvider instanceof MergedMinecraftProvider mergedMinecraftProvider) { + // These are merged v1 mappings + Files.deleteIfExists(tinyMappings); + LOGGER.info(":populating field names"); + suggestFieldNames(mergedMinecraftProvider, baseTinyMappings, tinyMappings); + } else { + throw new UnsupportedOperationException("V1 mappings only support merged minecraft"); } - - provider = new SrgProvider(getProject()); - getProject().getDependencies().add(provider.getTargetConfig(), "de.oceanlabs.mcp:mcp_config:" + getMinecraftProvider().minecraftVersion()); - Configuration configuration = getProject().getConfigurations().getByName(provider.getTargetConfig()); - provider.provide(DependencyInfo.create(getProject(), configuration.getDependencies().iterator().next(), configuration), postPopulationScheduler); } - - Path srgPath = getRawSrgFile(); - TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar); - TinyV2Writer.write(file, tinyMappings); } - private boolean isMCP(Path path) throws IOException { - try (FileSystem fs = FileSystems.newFileSystem(path, (ClassLoader) null)) { - return Files.exists(fs.getPath("fields.csv")) && Files.exists(fs.getPath("methods.csv")); + private 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 { try (BufferedReader reader = Files.newBufferedReader(path)) { return MappingReader.detectFormat(reader) == MappingFormat.TINY_2; - } catch (NoSuchFileException e) { - // TODO: just check the mappings version when Parser supports V1 in readMetadata() - return false; } } - private static boolean areMappingsMergedV2(Path path) throws IOException { - try (BufferedReader reader = Files.newBufferedReader(path)) { - return MappingReader.detectFormat(reader) == MappingFormat.TINY_2 && MappingReader.getNamespaces(reader).containsAll(Arrays.asList("named", "intermediary", "official")); - } catch (NoSuchFileException e) { - return false; - } - } - - 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; - } - } catch (NoSuchFileException e) { - return false; - } - } - - 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); } @@ -444,9 +275,9 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings ); } - private void populateUnpickClasspath() { + private void populateUnpickClasspath(Project project) { String unpickCliName = "unpick-cli"; - getProject().getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH, + project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH, String.format("%s:%s:%s", unpickMetadata.unpickGroup, unpickCliName, unpickMetadata.unpickVersion) ); @@ -459,83 +290,17 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings }; for (String asm : asmDeps) { - getProject().getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH, + project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH, asm.formatted(Opcodes.class.getPackage().getImplementationVersion()) ); } } - private void mergeAndSaveMappings(Project project, Path from, Path out) throws IOException { - Stopwatch stopwatch = Stopwatch.createStarted(); - project.getLogger().info(":merging mappings"); - - MemoryMappingTree tree = new MemoryMappingTree(); - MappingSourceNsSwitch sourceNsSwitch = new MappingSourceNsSwitch(tree, MappingsNamespace.OFFICIAL.toString()); - readIntermediaryTree().accept(sourceNsSwitch); - - try (BufferedReader reader = Files.newBufferedReader(from, StandardCharsets.UTF_8)) { - Tiny2Reader.read(reader, tree); - } - - inheritMappedNamesOfEnclosingClasses(tree); - - try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out, StandardCharsets.UTF_8), false)) { - tree.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(MinecraftProviderImpl minecraftProvider, Path oldMappings, Path newMappings) { + private void suggestFieldNames(MergedMinecraftProvider minecraftProvider, Path oldMappings, Path newMappings) { Command command = new CommandProposeFieldNames(); - runCommand(command, minecraftProvider.getMergedJar().getAbsolutePath(), - oldMappings.toAbsolutePath().toString(), - newMappings.toAbsolutePath().toString()); + runCommand(command, minecraftProvider.getMergedJar().toFile().getAbsolutePath(), + oldMappings.toAbsolutePath().toString(), + newMappings.toAbsolutePath().toString()); } private void runCommand(Command command, String... args) { @@ -546,22 +311,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings } } - private void initFiles() { - mappingsWorkingDir = getMinecraftProvider().dir(mappingsIdentifier).toPath(); - baseTinyMappings = mappingsWorkingDir.resolve("mappings-base.tiny"); - tinyMappings = mappingsWorkingDir.resolve("mappings.tiny"); - tinyMappingsJar = mappingsWorkingDir.resolve("mappings.jar"); - unpickDefinitions = mappingsWorkingDir.resolve("mappings.unpick"); - tinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-srg.tiny"); - mixinTinyMappingsWithSrg = mappingsWorkingDir.resolve("mappings-mixin-srg.tiny"); - srgToNamedSrg = mappingsWorkingDir.resolve("mappings-srg-named.srg"); - - if (isRefreshDeps()) { - cleanFiles(); - } - } - - public void cleanFiles() { + private static void cleanWorkingDirectory(Path mappingsWorkingDir) { try { if (Files.exists(mappingsWorkingDir)) { Files.walkFileTree(mappingsWorkingDir, new DeletingFileVisitor()); @@ -573,39 +323,20 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings } } - @Override - public String getTargetConfig() { - return Constants.Configurations.MAPPINGS; - } - - public Path getIntermediaryTiny() throws IOException { - if (intermediaryTiny == null) { - intermediaryTiny = getMinecraftProvider().file("intermediary-v2.tiny").toPath(); - - if (!Files.exists(intermediaryTiny) || (isRefreshDeps() && !hasRefreshed)) { - hasRefreshed = true; - - // Download and extract intermediary - String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(getMinecraftProvider().minecraftVersion()); - String intermediaryArtifactUrl = getExtension().getIntermediaryUrl(encodedMinecraftVersion); - File intermediaryJar = getMinecraftProvider().file("intermediary-v2.jar"); - DownloadUtil.downloadIfChanged(new URL(intermediaryArtifactUrl), intermediaryJar, getProject().getLogger()); - extractMappings(intermediaryJar.toPath(), intermediaryTiny); - } - } - - return intermediaryTiny; - } - @Override public Path mappingsWorkingDir() { return mappingsWorkingDir; } - protected 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 + "." + getMinecraftProvider().minecraftVersion().replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier; + return mappingsName + "." + minecraftVersion.replace(' ', '_').replace('.', '_').replace('-', '_') + "." + version + classifier; } public String mappingsIdentifier() { @@ -625,19 +356,19 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings 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); } public record UnpickMetadata(String unpickGroup, String unpickVersion) { } + + 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 83b3c5da..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,11 +24,7 @@ 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; @@ -36,9 +32,9 @@ 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(Supplier tinyFile) implements MappingLayer { +public record IntermediaryMappingLayer(Supplier memoryMappingTree) implements MappingLayer { @Override public MappingsNamespace getSourceNamespace() { return MappingsNamespace.OFFICIAL; @@ -49,8 +45,6 @@ public record IntermediaryMappingLayer(Supplier tinyFile) implements Mappi // 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().get().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 95277f52..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/configuration/providers/minecraft/MergedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java new file mode 100644 index 00000000..008cd5c0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java @@ -0,0 +1,96 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +import org.gradle.api.Project; + +import net.fabricmc.loom.util.HashedDownloadUtil; +import net.fabricmc.stitch.merge.JarMerger; + +public final class MergedMinecraftProvider extends MinecraftProvider { + private Path minecraftMergedJar; + + public MergedMinecraftProvider(Project project) { + super(project); + } + + @Override + protected void initFiles() { + super.initFiles(); + minecraftMergedJar = path("minecraft-merged.jar"); + } + + @Override + public List getMinecraftJars() { + return List.of(minecraftMergedJar); + } + + @Override + public void provide() throws Exception { + super.provide(); + + if (!Files.exists(minecraftMergedJar) || isRefreshDeps()) { + try { + mergeJars(); + } catch (Throwable e) { + HashedDownloadUtil.delete(getMinecraftClientJar()); + HashedDownloadUtil.delete(getMinecraftServerJar()); + Files.deleteIfExists(minecraftMergedJar); + + getProject().getLogger().error("Could not merge JARs! Deleting source JARs - please re-run the command and move on.", e); + throw e; + } + } + } + + private void mergeJars() throws IOException { + getLogger().info(":merging jars"); + + File jarToMerge = getMinecraftServerJar(); + + if (getServerBundleMetadata() != null) { + extractBundledServerJar(); + jarToMerge = getMinecraftExtractedServerJar(); + } + + Objects.requireNonNull(jarToMerge, "Cannot merge null input jar?"); + + try (JarMerger jarMerger = new JarMerger(getMinecraftClientJar(), jarToMerge, minecraftMergedJar.toFile())) { + jarMerger.enableSyntheticParamsOffset(); + jarMerger.merge(); + } + } + + public Path getMergedJar() { + return minecraftMergedJar; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java new file mode 100644 index 00000000..df949173 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarConfiguration.java @@ -0,0 +1,115 @@ +/* + * 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.minecraft; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.decompile.DecompileConfiguration; +import net.fabricmc.loom.configuration.decompile.SingleJarDecompileConfiguration; +import net.fabricmc.loom.configuration.decompile.SplitDecompileConfiguration; +import net.fabricmc.loom.configuration.processors.JarProcessorManager; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.ProcessedNamedMinecraftProvider; + +public enum MinecraftJarConfiguration { + MERGED( + MergedMinecraftProvider::new, + IntermediaryMinecraftProvider.MergedImpl::new, + NamedMinecraftProvider.MergedImpl::new, + ProcessedNamedMinecraftProvider.MergedImpl::new, + SingleJarDecompileConfiguration::new, + List.of("client", "server") + ), + SERVER_ONLY( + ServerOnlyMinecraftProvider::new, + IntermediaryMinecraftProvider.ServerOnlyImpl::new, + NamedMinecraftProvider.ServerOnlyImpl::new, + ProcessedNamedMinecraftProvider.ServerOnlyImpl::new, + SingleJarDecompileConfiguration::new, + List.of("server") + ), + SPLIT( + SplitMinecraftProvider::new, + IntermediaryMinecraftProvider.SplitImpl::new, + NamedMinecraftProvider.SplitImpl::new, + ProcessedNamedMinecraftProvider.SplitImpl::new, + SplitDecompileConfiguration::new, + List.of("client", "server") + ); + + private final Function minecraftProviderFunction; + private final BiFunction> intermediaryMinecraftProviderBiFunction; + private final BiFunction> namedMinecraftProviderBiFunction; + private final BiFunction, JarProcessorManager, ProcessedNamedMinecraftProvider> processedNamedMinecraftProviderBiFunction; + private final BiFunction> decompileConfigurationBiFunction; + private final List supportedEnvironments; + + @SuppressWarnings("unchecked") // Just a bit of a generic mess :) + , Q extends MappedMinecraftProvider> MinecraftJarConfiguration( + Function minecraftProviderFunction, + BiFunction> intermediaryMinecraftProviderBiFunction, + BiFunction namedMinecraftProviderBiFunction, + BiFunction> processedNamedMinecraftProviderBiFunction, + BiFunction> decompileConfigurationBiFunction, + List supportedEnvironments + ) { + this.minecraftProviderFunction = (Function) minecraftProviderFunction; + this.intermediaryMinecraftProviderBiFunction = (BiFunction>) (Object) intermediaryMinecraftProviderBiFunction; + this.namedMinecraftProviderBiFunction = (BiFunction>) namedMinecraftProviderBiFunction; + this.processedNamedMinecraftProviderBiFunction = (BiFunction, JarProcessorManager, ProcessedNamedMinecraftProvider>) (Object) processedNamedMinecraftProviderBiFunction; + this.decompileConfigurationBiFunction = (BiFunction>) decompileConfigurationBiFunction; + this.supportedEnvironments = supportedEnvironments; + } + + public Function getMinecraftProviderFunction() { + return minecraftProviderFunction; + } + + public BiFunction> getIntermediaryMinecraftProviderBiFunction() { + return intermediaryMinecraftProviderBiFunction; + } + + public BiFunction> getNamedMinecraftProviderBiFunction() { + return namedMinecraftProviderBiFunction; + } + + public BiFunction, JarProcessorManager, ProcessedNamedMinecraftProvider> getProcessedNamedMinecraftProviderBiFunction() { + return processedNamedMinecraftProviderBiFunction; + } + + public BiFunction> getDecompileConfigurationBiFunction() { + return decompileConfigurationBiFunction; + } + + public List getSupportedEnvironments() { + return supportedEnvironments; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java new file mode 100644 index 00000000..87d7a800 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java @@ -0,0 +1,169 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.stream.Stream; + +import com.google.common.collect.Sets; + +import net.fabricmc.loom.util.FileSystemUtil; + +public class MinecraftJarSplitter implements AutoCloseable { + private final Path clientInputJar; + private final Path serverInputJar; + + private EntryData entryData; + private Set sharedEntries = new HashSet<>(); + private Set forcedClientEntries = new HashSet<>(); + + public MinecraftJarSplitter(Path clientInputJar, Path serverInputJar) { + this.clientInputJar = Objects.requireNonNull(clientInputJar); + this.serverInputJar = Objects.requireNonNull(serverInputJar); + } + + public void split(Path clientOnlyOutputJar, Path commonOutputJar) throws IOException { + Objects.requireNonNull(clientOnlyOutputJar); + Objects.requireNonNull(commonOutputJar); + + if (entryData == null) { + entryData = new EntryData(getJarEntries(clientInputJar), getJarEntries(serverInputJar)); + } + + // Not something we expect, will require 3 jars, server, client and common. + assert entryData.serverOnlyEntries.isEmpty(); + + copyEntriesToJar(entryData.commonEntries, serverInputJar, commonOutputJar); + copyEntriesToJar(entryData.clientOnlyEntries, clientInputJar, clientOnlyOutputJar); + } + + public void sharedEntry(String path) { + this.sharedEntries.add(path); + } + + public void forcedClientEntry(String path) { + this.forcedClientEntries.add(path); + } + + private Set getJarEntries(Path input) throws IOException { + Set entries = Sets.newHashSet(); + + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(input); + Stream walk = Files.walk(fs.get().getPath("/"))) { + Iterator iterator = walk.iterator(); + + while (iterator.hasNext()) { + Path fsPath = iterator.next(); + + if (!Files.isRegularFile(fsPath)) { + continue; + } + + String entryPath = fs.get().getPath("/").relativize(fsPath).toString(); + + if (entryPath.startsWith("META-INF/")) { + continue; + } + + entries.add(entryPath); + } + } + + return entries; + } + + private void copyEntriesToJar(Set entries, Path inputJar, Path outputJar) throws IOException { + Files.deleteIfExists(outputJar); + + try (FileSystemUtil.Delegate inputFs = FileSystemUtil.getJarFileSystem(inputJar); + FileSystemUtil.Delegate outputFs = FileSystemUtil.getJarFileSystem(outputJar, true)) { + for (String entry : entries) { + Path inputPath = inputFs.get().getPath(entry); + Path outputPath = outputFs.get().getPath(entry); + + assert Files.isRegularFile(inputPath); + + Path outputPathParent = outputPath.getParent(); + + if (outputPathParent != null) { + Files.createDirectories(outputPathParent); + } + + Files.copy(inputPath, outputPath, StandardCopyOption.COPY_ATTRIBUTES); + } + + writeManifest(outputFs); + } + } + + private void writeManifest(FileSystemUtil.Delegate outputFs) throws IOException { + final Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + manifest.write(out); + Files.createDirectories(outputFs.get().getPath("META-INF")); + Files.write(outputFs.get().getPath("META-INF/MANIFEST.MF"), out.toByteArray()); + } + + @Override + public void close() throws Exception { + } + + private final class EntryData { + private final Set clientEntries; + private final Set serverEntries; + private final Set commonEntries; + private final Set clientOnlyEntries; + private final Set serverOnlyEntries; + + private EntryData(Set clientEntries, Set serverEntries) { + this.clientEntries = clientEntries; + this.serverEntries = serverEntries; + + this.commonEntries = Sets.newHashSet(clientEntries); + this.commonEntries.retainAll(serverEntries); + this.commonEntries.addAll(sharedEntries); + this.commonEntries.removeAll(forcedClientEntries); + + this.clientOnlyEntries = Sets.newHashSet(clientEntries); + this.clientOnlyEntries.removeAll(serverEntries); + this.clientOnlyEntries.addAll(sharedEntries); + this.clientOnlyEntries.addAll(forcedClientEntries); + + this.serverOnlyEntries = Sets.newHashSet(serverEntries); + this.serverOnlyEntries.removeAll(clientEntries); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java index 3e4a3c49..567a1479 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java @@ -30,16 +30,17 @@ import java.util.regex.Pattern; import org.gradle.api.Project; import org.gradle.api.artifacts.ExternalModuleDependency; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.providers.BundleMetadata; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; import net.fabricmc.loom.util.Constants; public class MinecraftLibraryProvider { private static final Pattern NATIVES_PATTERN = Pattern.compile("^(?.*)/(.*?)/(?.*)/((?.*?)-([0-9].*?)-)(?.*).jar$"); - public void provide(MinecraftProviderImpl minecraftProvider, Project project) { - MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo(); - BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata(); + public void provide(MinecraftProvider minecraftProvider, Project project) { + final MinecraftJarConfiguration jarConfiguration = LoomGradleExtension.get(project).getMinecraftJarConfiguration().get(); + final MinecraftVersionMeta versionInfo = minecraftProvider.getVersionInfo(); + final BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata(); final boolean overrideLWJGL = LWJGLVersionOverride.overrideByDefault() || LWJGLVersionOverride.forceOverride(project) || Boolean.getBoolean("loom.test.lwjgloverride"); @@ -55,7 +56,7 @@ public class MinecraftLibraryProvider { if (library.isValidForOS() && !library.hasNatives() && library.artifact() != null) { if (serverBundleMetadata != null && isLibraryInBundle(serverBundleMetadata, library)) { project.getDependencies().add(Constants.Configurations.MINECRAFT_SERVER_DEPENDENCIES, library.name()); - } else { + } else if (jarConfiguration.getSupportedEnvironments().contains("client")) { // Client only library, or legacy version project.getDependencies().add(Constants.Configurations.MINECRAFT_DEPENDENCIES, library.name()); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java deleted file mode 100644 index fbfd276a..00000000 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2016-2021 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.configuration.providers.minecraft; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; - -import com.google.common.base.Stopwatch; -import dev.architectury.tinyremapper.IMappingProvider; -import dev.architectury.tinyremapper.InputTag; -import dev.architectury.tinyremapper.NonClassCopyMode; -import dev.architectury.tinyremapper.OutputConsumerPath; -import dev.architectury.tinyremapper.TinyRemapper; -import org.apache.commons.lang3.mutable.Mutable; -import org.apache.commons.lang3.tuple.Triple; -import org.gradle.api.Project; -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; -import net.fabricmc.loom.configuration.DependencyProvider; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; -import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; -import net.fabricmc.loom.configuration.providers.minecraft.tr.OutputRemappingHandler; -import net.fabricmc.loom.configuration.sources.ForgeSourcesRemapper; -import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.DownloadUtil; -import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.OperatingSystem; -import net.fabricmc.loom.util.ThreadingUtils; -import net.fabricmc.loom.util.TinyRemapperHelper; -import net.fabricmc.loom.util.srg.AtRemapper; -import net.fabricmc.loom.util.srg.CoreModClassRemapper; -import net.fabricmc.loom.util.srg.InnerClassRemapper; -import net.fabricmc.mappingio.tree.MemoryMappingTree; - -public class MinecraftMappedProvider extends DependencyProvider { - private File inputJar; - private File inputForgeJar; - private File minecraftMappedJar; - private File minecraftIntermediaryJar; - private File minecraftSrgJar; - private File forgeMappedJar; - private File forgeIntermediaryJar; - private File forgeSrgJar; - - protected MinecraftProviderImpl minecraftProvider; - - public MinecraftMappedProvider(Project project) { - super(project); - } - - @Override - public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { - if (Files.notExists(getExtension().getMappingsProvider().tinyMappings)) { - throw new RuntimeException("mappings file not found"); - } - - if (!inputJar.exists()) { - throw new RuntimeException("input merged jar not found"); - } - - boolean isForgeAtDirty = getExtension().isForge() && getExtension().getMappingsProvider().patchedProvider.isAtDirty(); - boolean needToRemap = false; - - if (!minecraftMappedJar.exists() || !getIntermediaryJar().exists() || (getExtension().isForge() && !getSrgJar().exists()) || isRefreshDeps() || isForgeAtDirty) { - needToRemap = true; - } - - if (getExtension().isForgeAndNotOfficial() && (!getForgeMappedJar().exists() || !getForgeIntermediaryJar().exists() || !getForgeSrgJar().exists() || isRefreshDeps() || isForgeAtDirty)) { - needToRemap = true; - } - - if (needToRemap) { - if (minecraftMappedJar.exists()) { - minecraftMappedJar.delete(); - } - - minecraftMappedJar.getParentFile().mkdirs(); - - if (minecraftIntermediaryJar.exists()) { - minecraftIntermediaryJar.delete(); - } - - if (getExtension().isForge() && minecraftSrgJar.exists()) { - minecraftSrgJar.delete(); - } - - if (getExtension().isForgeAndNotOfficial()) { - if (getForgeMappedJar().exists()) { - getForgeMappedJar().delete(); - } - - getForgeMappedJar().getParentFile().mkdirs(); - getForgeIntermediaryJar().delete(); - getForgeSrgJar().delete(); - } - - try { - TinyRemapper[] remapperArray = new TinyRemapper[] {null}; - mapMinecraftJar(remapperArray); - remapperArray[0].finish(); - } catch (Throwable t) { - // Cleanup some some things that may be in a bad state now - DownloadUtil.delete(minecraftMappedJar); - DownloadUtil.delete(minecraftIntermediaryJar); - getExtension().getMinecraftProvider().deleteFiles(); - - if (getExtension().isForge()) { - DownloadUtil.delete(minecraftSrgJar); - DownloadUtil.delete(forgeMappedJar); - DownloadUtil.delete(forgeSrgJar); - DownloadUtil.delete(forgeIntermediaryJar); - } - - getExtension().getMappingsProvider().cleanFiles(); - throw new RuntimeException("Failed to remap minecraft", t); - } - } - - if (!minecraftMappedJar.exists()) { - throw new RuntimeException("mapped jar not found"); - } - - addDependencies(dependency, postPopulationScheduler); - - if (getExtension().isForgeAndNotOfficial()) { - getProject().getDependencies().add(Constants.Configurations.FORGE_NAMED, - getProject().getDependencies().module("net.minecraftforge-loom:forge-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier() + "/forge")); - } - - if (getExtension().isForge()) { - getProject().afterEvaluate(project -> { - if (!OperatingSystem.isCIBuild()) { - try { - ForgeSourcesRemapper.addBaseForgeSources(project, getExtension().isForgeAndOfficial()); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - } - } - - private byte[][] inputBytes(Path input) throws IOException { - List inputByteList = new ArrayList<>(); - - try (FileSystemUtil.Delegate inputFs = FileSystemUtil.getJarFileSystem(input, false)) { - ThreadingUtils.TaskCompleter taskCompleter = ThreadingUtils.taskCompleter(); - - for (Path path : (Iterable) Files.walk(inputFs.get().getPath("/"))::iterator) { - if (Files.isRegularFile(path)) { - if (path.getFileName().toString().endsWith(".class")) { - taskCompleter.add(() -> { - byte[] bytes = Files.readAllBytes(path); - - synchronized (inputByteList) { - inputByteList.add(bytes); - } - }); - } - } - } - - taskCompleter.complete(); - } - - return inputByteList.toArray(new byte[0][0]); - } - - private void assetsOut(Path input, @Nullable Path assetsOut) throws IOException { - if (assetsOut != null) { - try (OutputConsumerPath tmpAssetsPath = new OutputConsumerPath.Builder(assetsOut).assumeArchive(true).build()) { - if (getExtension().isForge()) { - tmpAssetsPath.addNonClassFiles(input, NonClassCopyMode.FIX_META_INF, null); - } else { - tmpAssetsPath.addNonClassFiles(input); - } - } - } - } - - private void mapMinecraftJar(TinyRemapper[] remapperArray) throws Exception { - Path input = inputJar.toPath(); - Path inputForge = inputForgeJar == null ? null : inputForgeJar.toPath(); - Path outputMapped = minecraftMappedJar.toPath(); - Path outputIntermediary = minecraftIntermediaryJar.toPath(); - Path outputSrg = minecraftSrgJar == null ? null : minecraftSrgJar.toPath(); - - Path forgeOutputMapped = forgeMappedJar == null ? null : forgeMappedJar.toPath(); - Path forgeOutputIntermediary = forgeIntermediaryJar == null ? null : forgeIntermediaryJar.toPath(); - Path forgeOutputSrg = forgeSrgJar == null ? null : forgeSrgJar.toPath(); - - Path vanillaAssets = Files.createTempFile("assets", null); - Files.deleteIfExists(vanillaAssets); - vanillaAssets.toFile().deleteOnExit(); - Path forgeAssets = Files.createTempFile("assets", null); - Files.deleteIfExists(forgeAssets); - forgeAssets.toFile().deleteOnExit(); - - Info vanilla = new Info(vanillaAssets, input, outputMapped, outputIntermediary, outputSrg); - Info forge = getExtension().isForgeAndNotOfficial() ? new Info(forgeAssets, inputForge, forgeOutputMapped, forgeOutputIntermediary, forgeOutputSrg) : null; - - Triple, List> pair = TinyRemapperHelper.getTinyRemapper(getProject(), true, builder -> { }); - TinyRemapper remapper = remapperArray[0] = pair.getLeft(); - - assetsOut(input, vanillaAssets); - - if (getExtension().isForgeAndNotOfficial()) { - assetsOut(inputForge, forgeAssets); - } - - remap(remapper, pair.getMiddle(), pair.getRight(), vanilla, forge, MappingsNamespace.OFFICIAL.toString()); - } - - public static class Info { - Path assets; - Path input; - Path outputMapped; - Path outputIntermediary; - Path outputSrg; - - public Info(Path assets, Path input, Path outputMapped, Path outputIntermediary, Path outputSrg) { - this.assets = assets; - this.input = input; - this.outputMapped = outputMapped; - this.outputIntermediary = outputIntermediary; - this.outputSrg = outputSrg; - } - } - - public void remap(TinyRemapper remapper, Mutable mappings, List postApply, Info vanilla, @Nullable Info forge, String fromM) throws IOException { - Set classNames = getExtension().isForge() ? InnerClassRemapper.readClassNames(vanilla.input) : null; - - for (String toM : getExtension().isForge() ? Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.SRG.toString(), MappingsNamespace.NAMED.toString()) : Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.NAMED.toString())) { - Path output = MappingsNamespace.NAMED.toString().equals(toM) ? vanilla.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? vanilla.outputSrg : vanilla.outputIntermediary; - Path outputForge = forge == null ? null : MappingsNamespace.NAMED.toString().equals(toM) ? forge.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? forge.outputSrg : forge.outputIntermediary; - InputTag vanillaTag = remapper.createInputTag(); - InputTag forgeTag = remapper.createInputTag(); - Stopwatch stopwatch = Stopwatch.createStarted(); - getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, " + fromM + " -> " + toM + ")"); - - remapper.readInputs(vanillaTag, vanilla.input); - - if (forge != null) { - remapper.readInputs(forgeTag, forge.input); - } - - remapper.replaceMappings(getMappings(classNames, fromM, toM, mappings)); - if (!MappingsNamespace.INTERMEDIARY.toString().equals(toM)) mappings.setValue(null); - postApply.clear(); - - postApply.add(new SignatureFixerApplyVisitor(SignatureFixerApplyVisitor.getRemappedSignatures(MappingsNamespace.INTERMEDIARY.toString().equals(toM), getExtension().getMappingsProvider(), getProject(), toM))); - - OutputRemappingHandler.remap(remapper, vanilla.assets, output, null, vanillaTag); - - if (forge != null) { - OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag); - } - - getProject().getLogger().lifecycle(":remapped minecraft (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch); - remapper.removeInput(); - mappings.setValue(null); - - if (getExtension().isForge() && !"srg".equals(toM)) { - getProject().getLogger().info(":running minecraft finalising tasks"); - - MemoryMappingTree yarnWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - AtRemapper.remap(getProject().getLogger(), output, yarnWithSrg); - CoreModClassRemapper.remapJar(output, yarnWithSrg, getProject().getLogger()); - } - } - } - - public Set getMappings(@Nullable Set fromClassNames, String fromM, String toM, Mutable mappings) throws IOException { - Set providers = new HashSet<>(); - mappings.setValue(getExtension().isForge() ? getExtension().getMappingsProvider().getMappingsWithSrg() : getExtension().getMappingsProvider().getMappings()); - providers.add(TinyRemapperHelper.create(mappings.getValue(), fromM, toM, true)); - - if (getExtension().isForge()) { - if (fromClassNames != null) { - providers.add(InnerClassRemapper.of(fromClassNames, getExtension().getMappingsProvider().getMappingsWithSrg(), fromM, toM)); - } - } else { - providers.add(out -> TinyRemapperHelper.JSR_TO_JETBRAINS.forEach(out::acceptClass)); - } - - return providers; - } - - protected void addDependencies(DependencyInfo dependency, Consumer postPopulationScheduler) { - getProject().getDependencies().add(Constants.Configurations.MINECRAFT_NAMED, - getProject().getDependencies().module("net.minecraft:" + minecraftProvider.getJarPrefix() + "minecraft-mapped:" + getMinecraftProvider().minecraftVersion() + "/" + getExtension().getMappingsProvider().mappingsIdentifier())); - } - - public void initFiles(MinecraftProviderImpl minecraftProvider, MappingsProviderImpl mappingsProvider) { - this.minecraftProvider = minecraftProvider; - minecraftIntermediaryJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-intermediary.jar"); - minecraftSrgJar = !getExtension().isForge() ? null : new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-srg.jar"); - minecraftMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), minecraftProvider.getJarPrefix() + "minecraft-mapped.jar"); - inputJar = getExtension().isForge() ? mappingsProvider.patchedProvider.getMergedJar() : minecraftProvider.getMergedJar(); - - if (getExtension().isForgeAndNotOfficial()) { - inputForgeJar = mappingsProvider.patchedProvider.getForgeMergedJar(); - forgeIntermediaryJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-intermediary.jar"); - forgeSrgJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-srg.jar"); - forgeMappedJar = new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "forge/forge-mapped.jar"); - } else { - inputForgeJar = null; - forgeIntermediaryJar = null; - forgeSrgJar = null; - forgeMappedJar = null; - } - } - - protected File getJarDirectory(File parentDirectory, String type) { - return new File(parentDirectory, getJarVersionString(type)); - } - - protected String getJarVersionString(String type) { - return String.format("%s-%s%s", type, getExtension().getMappingsProvider().mappingsIdentifier(), minecraftProvider.getJarPrefix()); - } - - public File getIntermediaryJar() { - return minecraftIntermediaryJar; - } - - public File getSrgJar() { - return minecraftSrgJar; - } - - public File getMappedJar() { - return minecraftMappedJar; - } - - public final File getBaseMappedJar() { - return minecraftMappedJar; - } - - public File getForgeIntermediaryJar() { - return forgeIntermediaryJar; - } - - public File getForgeSrgJar() { - return forgeSrgJar; - } - - public File getForgeMappedJar() { - return forgeMappedJar; - } - - public File getUnpickedJar() { - return new File(getExtension().getMappingsProvider().mappingsWorkingDir().toFile(), "minecraft-unpicked.jar"); - } - - @Override - public String getTargetConfig() { - return Constants.Configurations.MINECRAFT_NAMED; - } -} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java similarity index 73% rename from src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java rename to src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java index aab3fff4..39df7e25 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java @@ -22,35 +22,34 @@ * SOFTWARE. */ -package net.fabricmc.loom.configuration.providers; +package net.fabricmc.loom.configuration.providers.minecraft; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; import java.util.Optional; -import java.util.function.Consumer; -import com.google.common.base.Stopwatch; import com.google.common.io.Files; import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.logging.Logger; import org.jetbrains.annotations.Nullable; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; -import net.fabricmc.loom.configuration.DependencyProvider; -import net.fabricmc.loom.configuration.providers.minecraft.ManifestVersion; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftLibraryProvider; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; +import net.fabricmc.loom.configuration.DependencyInfo; +import net.fabricmc.loom.configuration.providers.BundleMetadata; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DownloadUtil; import net.fabricmc.loom.util.HashedDownloadUtil; import net.fabricmc.loom.util.MirrorUtil; -import net.fabricmc.stitch.merge.JarMerger; -public class MinecraftProviderImpl extends DependencyProvider implements MinecraftProvider { +public abstract class MinecraftProvider { private String minecraftVersion; private MinecraftVersionMeta versionInfo; @@ -58,30 +57,26 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra private File workingDir; private File minecraftJson; - public File minecraftClientJar; + private File minecraftClientJar; // Note this will be the boostrap jar starting with 21w39a - public File minecraftServerJar; + private File minecraftServerJar; // The extracted server jar from the boostrap, only exists in >=21w39a - public File minecraftExtractedServerJar; + private File minecraftExtractedServerJar; @Nullable - public BundleMetadata serverBundleMetadata; - private File minecraftMergedJar; + private BundleMetadata serverBundleMetadata; private File versionManifestJson; private File experimentalVersionsJson; - private String jarPrefix = ""; - public MinecraftProviderImpl(Project project) { - super(project); + private final Project project; + + public MinecraftProvider(Project project) { + this.project = project; } - @Override - public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { + public void provide() throws Exception { + final DependencyInfo dependency = DependencyInfo.create(getProject(), Constants.Configurations.MINECRAFT); minecraftVersion = dependency.getDependency().getVersion(); - if (getExtension().shouldGenerateSrgTiny() && !getExtension().isForge()) { - addDependency("de.oceanlabs.mcp:mcp_config:" + minecraftVersion, Constants.Configurations.SRG); - } - boolean offline = getProject().getGradle().getStartParameter().isOffline(); initFiles(); @@ -95,9 +90,6 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra if (offline) { if (minecraftClientJar.exists() && minecraftServerJar.exists()) { getProject().getLogger().debug("Found client and server jars, presuming up-to-date"); - } else if (minecraftMergedJar.exists()) { - //Strictly we don't need the split jars if the merged one exists, let's try go on - getProject().getLogger().warn("Missing game jar but merged jar present, things might end badly"); } else { throw new GradleException("Missing jar(s); Client: " + minecraftClientJar.exists() + ", Server: " + minecraftServerJar.exists()); } @@ -109,39 +101,17 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra libraryProvider = new MinecraftLibraryProvider(); libraryProvider.provide(this, getProject()); - - if (!minecraftMergedJar.exists() || isRefreshDeps()) { - try { - mergeJars(getProject().getLogger()); - } catch (Throwable e) { - HashedDownloadUtil.delete(minecraftClientJar); - HashedDownloadUtil.delete(minecraftServerJar); - minecraftMergedJar.delete(); - - getProject().getLogger().error("Could not merge JARs! Deleting source JARs - please re-run the command and move on.", e); - throw e; - } - } } - private void initFiles() { - workingDir = new File(getDirectories().getUserCache(), minecraftVersion); + protected void initFiles() { + workingDir = new File(getExtension().getFiles().getUserCache(), minecraftVersion); workingDir.mkdirs(); minecraftJson = file("minecraft-info.json"); minecraftClientJar = file("minecraft-client.jar"); minecraftServerJar = file("minecraft-server.jar"); minecraftExtractedServerJar = file("minecraft-extracted_server.jar"); - minecraftMergedJar = file("minecraft-merged.jar"); - versionManifestJson = new File(getDirectories().getUserCache(), "version_manifest.json"); - experimentalVersionsJson = new File(getDirectories().getUserCache(), "experimental_version_manifest.json"); - } - - public void deleteFiles() { - DownloadUtil.delete(minecraftClientJar); - DownloadUtil.delete(minecraftServerJar); - DownloadUtil.delete(minecraftMergedJar); - DownloadUtil.delete(versionManifestJson); - DownloadUtil.delete(experimentalVersionsJson); + versionManifestJson = new File(getExtension().getFiles().getUserCache(), "version_manifest.json"); + experimentalVersionsJson = new File(getExtension().getFiles().getUserCache(), "experimental_version_manifest.json"); } private void downloadMcJson(boolean offline) throws IOException { @@ -268,58 +238,55 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra HashedDownloadUtil.downloadIfInvalid(new URL(server.url()), minecraftServerJar, server.sha1(), logger, false); } - private void mergeJars(Logger logger) throws IOException { - logger.info(":merging jars"); - Stopwatch stopwatch = Stopwatch.createStarted(); + protected final void extractBundledServerJar() throws IOException { + Objects.requireNonNull(getServerBundleMetadata(), "Cannot bundled mc jar from none bundled server jar"); - File jarToMerge = minecraftServerJar; + getLogger().info(":Extracting server jar from bootstrap"); - if (serverBundleMetadata != null) { - logger.info(":Extracting server jar from bootstrap"); - - if (serverBundleMetadata.versions().size() != 1) { - throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(serverBundleMetadata.versions().size())); - } - - serverBundleMetadata.versions().get(0).unpackEntry(minecraftServerJar.toPath(), minecraftExtractedServerJar.toPath()); - jarToMerge = minecraftExtractedServerJar; + if (getServerBundleMetadata().versions().size() != 1) { + throw new UnsupportedOperationException("Expected only 1 version in META-INF/versions.list, but got %d".formatted(getServerBundleMetadata().versions().size())); } - try (JarMerger jarMerger = new JarMerger(minecraftClientJar, jarToMerge, minecraftMergedJar)) { - jarMerger.enableSyntheticParamsOffset(); - jarMerger.merge(); - } - - logger.info(":merged jars in " + stopwatch); + getServerBundleMetadata().versions().get(0).unpackEntry(minecraftServerJar.toPath(), getMinecraftExtractedServerJar().toPath()); } - public File getMergedJar() { - return minecraftMergedJar; - } - - @Override public File workingDir() { return workingDir; } - @Override public File dir(String path) { File dir = file(path); dir.mkdirs(); return dir; } - @Override public File file(String path) { return new File(workingDir(), path); } - @Override + public Path path(String path) { + return file(path).toPath(); + } + + public File getMinecraftClientJar() { + return minecraftClientJar; + } + + // May be null on older versions + @Nullable + public File getMinecraftExtractedServerJar() { + return minecraftExtractedServerJar; + } + + // This may be the server bundler jar on newer versions prob not what you want. + public File getMinecraftServerJar() { + return minecraftServerJar; + } + public String minecraftVersion() { return minecraftVersion; } - @Override public MinecraftVersionMeta getVersionInfo() { return versionInfo; } @@ -328,15 +295,6 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra return libraryProvider; } - public String getJarPrefix() { - return jarPrefix; - } - - public void setJarPrefix(String jarSuffix) { - this.jarPrefix = jarSuffix; - } - - @Override public String getTargetConfig() { return Constants.Configurations.MINECRAFT; } @@ -345,4 +303,22 @@ public class MinecraftProviderImpl extends DependencyProvider implements Minecra public BundleMetadata getServerBundleMetadata() { return serverBundleMetadata; } + + protected Logger getLogger() { + return getProject().getLogger(); + } + + public abstract List getMinecraftJars(); + + protected Project getProject() { + return project; + } + + protected LoomGradleExtension getExtension() { + return LoomGradleExtension.get(getProject()); + } + + protected boolean isRefreshDeps() { + return LoomGradlePlugin.refreshDeps; + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ServerOnlyMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ServerOnlyMinecraftProvider.java new file mode 100644 index 00000000..a12343f1 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/ServerOnlyMinecraftProvider.java @@ -0,0 +1,102 @@ +/* + * 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.minecraft; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.providers.BundleMetadata; +import net.fabricmc.tinyremapper.NonClassCopyMode; +import net.fabricmc.tinyremapper.OutputConsumerPath; +import net.fabricmc.tinyremapper.TinyRemapper; + +public final class ServerOnlyMinecraftProvider extends MinecraftProvider { + private Path minecraftServerOnlyJar; + + public ServerOnlyMinecraftProvider(Project project) { + super(project); + } + + @Override + protected void initFiles() { + super.initFiles(); + + minecraftServerOnlyJar = path("minecraft-server-only.jar"); + } + + @Override + public List getMinecraftJars() { + return List.of(minecraftServerOnlyJar); + } + + @Override + public void provide() throws Exception { + super.provide(); + + boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftServerOnlyJar); + + if (!requiresRefresh) { + return; + } + + BundleMetadata serverBundleMetadata = getServerBundleMetadata(); + + if (serverBundleMetadata == null) { + throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar support server only configuration, please use a merged jar setup for this version of minecraft"); + } + + extractBundledServerJar(); + final Path serverJar = getMinecraftExtractedServerJar().toPath(); + + TinyRemapper remapper = null; + + try { + remapper = TinyRemapper.newRemapper().build(); + + Files.deleteIfExists(minecraftServerOnlyJar); + + // Pass through tiny remapper to fix the meta-inf + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(minecraftServerOnlyJar).build()) { + outputConsumer.addNonClassFiles(serverJar, NonClassCopyMode.FIX_META_INF, remapper); + remapper.readInputs(serverJar); + remapper.apply(outputConsumer); + } + } catch (Exception e) { + Files.deleteIfExists(minecraftServerOnlyJar); + throw new RuntimeException("Failed to process server only jar", e); + } finally { + if (remapper != null) { + remapper.finish(); + } + } + } + + public Path getMinecraftServerOnlyJar() { + return minecraftServerOnlyJar; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java new file mode 100644 index 00000000..319b33e2 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java @@ -0,0 +1,98 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.gradle.api.Project; + +import net.fabricmc.loom.configuration.providers.BundleMetadata; + +public final class SplitMinecraftProvider extends MinecraftProvider { + private Path minecraftClientOnlyJar; + private Path minecraftCommonJar; + + public SplitMinecraftProvider(Project project) { + super(project); + } + + @Override + protected void initFiles() { + super.initFiles(); + + minecraftClientOnlyJar = path("minecraft-client-only.jar"); + minecraftCommonJar = path("minecraft-common.jar"); + } + + @Override + public List getMinecraftJars() { + return List.of(minecraftClientOnlyJar, minecraftCommonJar); + } + + @Override + public void provide() throws Exception { + super.provide(); + + boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftClientOnlyJar) || Files.notExists(minecraftCommonJar); + + if (!requiresRefresh) { + return; + } + + BundleMetadata serverBundleMetadata = getServerBundleMetadata(); + + if (serverBundleMetadata == null) { + throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar can be split, please use a merged jar setup for this version of minecraft"); + } + + extractBundledServerJar(); + + final Path clientJar = getMinecraftClientJar().toPath(); + final Path serverJar = getMinecraftExtractedServerJar().toPath(); + + try (MinecraftJarSplitter jarSplitter = new MinecraftJarSplitter(clientJar, serverJar)) { + // Required for loader to compute the version info also useful to have in both jars. + jarSplitter.sharedEntry("version.json"); + jarSplitter.forcedClientEntry("assets/.mcassetsroot"); + + jarSplitter.split(minecraftClientOnlyJar, minecraftCommonJar); + } catch (Exception e) { + Files.deleteIfExists(minecraftClientOnlyJar); + Files.deleteIfExists(minecraftCommonJar); + + throw new RuntimeException("Failed to split minecraft", e); + } + } + + public Path getMinecraftClientOnlyJar() { + return minecraftClientOnlyJar; + } + + public Path getMinecraftCommonJar() { + return minecraftCommonJar; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java index de49c3ba..5baec839 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/assets/MinecraftAssetsProvider.java @@ -43,13 +43,13 @@ import org.gradle.api.Project; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; import net.fabricmc.loom.util.MirrorUtil; import net.fabricmc.loom.util.HashedDownloadUtil; public class MinecraftAssetsProvider { - public static void provide(MinecraftProviderImpl minecraftProvider, Project project) throws IOException { + public static void provide(MinecraftProvider minecraftProvider, Project project) throws IOException { LoomGradleExtension extension = LoomGradleExtension.get(project); boolean offline = project.getGradle().getStartParameter().isOffline(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java new file mode 100644 index 00000000..68239f4d --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java @@ -0,0 +1,166 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft.mapped; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +import org.gradle.api.Project; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.SignatureFixerApplyVisitor; +import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.tinyremapper.OutputConsumerPath; +import net.fabricmc.tinyremapper.TinyRemapper; + +public abstract class AbstractMappedMinecraftProvider implements MappedMinecraftProvider.ProviderImpl { + protected final M minecraftProvider; + private final Project project; + protected final LoomGradleExtension extension; + + public AbstractMappedMinecraftProvider(Project project, M minecraftProvider) { + this.project = project; + this.minecraftProvider = minecraftProvider; + this.extension = LoomGradleExtension.get(project); + } + + public abstract MappingsNamespace getTargetNamespace(); + + public abstract List getRemappedJars(); + + protected void applyDependencies(BiConsumer consumer) { + // Override if needed + } + + public void provide(boolean applyDependencies) throws Exception { + final List remappedJars = getRemappedJars(); + assert !remappedJars.isEmpty(); + + if (!areOutputsValid(remappedJars) || LoomGradlePlugin.refreshDeps) { + try { + remapInputs(remappedJars); + } catch (Throwable t) { + cleanOutputs(remappedJars); + + throw new RuntimeException("Failed to remap minecraft", t); + } + } + + if (applyDependencies) { + applyDependencies((configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name))); + } + } + + protected abstract Path getDirectory(); + + @Override + public Path getJar(String name) { + return getDirectory().resolve(getName(name) + ".jar"); + } + + protected String getName(String name) { + return "minecraft-%s-%s".formatted(name, getTargetNamespace().toString()); + } + + protected String getDependencyNotation(String name) { + return "net.minecraft:%s:%s/%s".formatted(getName(name), extension.getMinecraftProvider().minecraftVersion(), extension.getMappingsProvider().mappingsIdentifier()); + } + + private boolean areOutputsValid(List remappedJars) { + for (RemappedJars remappedJar : remappedJars) { + if (!Files.exists(remappedJar.outputJar())) { + return false; + } + } + + return true; + } + + private void remapInputs(List remappedJars) throws IOException { + cleanOutputs(remappedJars); + + for (RemappedJars remappedJar : remappedJars) { + remapJar(remappedJar); + } + } + + private void remapJar(RemappedJars remappedJars) throws IOException { + final MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); + final String fromM = remappedJars.sourceNamespace().toString(); + final String toM = getTargetNamespace().toString(); + + Files.deleteIfExists(remappedJars.outputJar()); + + final Map remappedSignatures = SignatureFixerApplyVisitor.getRemappedSignatures(getTargetNamespace() == MappingsNamespace.INTERMEDIARY, mappingsProvider, project, toM); + TinyRemapper remapper = TinyRemapperHelper.getTinyRemapper(project, fromM, toM, true, (builder) -> { + builder.extraPostApplyVisitor(new SignatureFixerApplyVisitor(remappedSignatures)); + configureRemapper(remappedJars, builder); + }); + + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(remappedJars.outputJar()).build()) { + outputConsumer.addNonClassFiles(remappedJars.inputJar()); + remapper.readClassPath(TinyRemapperHelper.getMinecraftDependencies(project)); + + for (Path path : remappedJars.remapClasspath()) { + remapper.readClassPath(path); + } + + remapper.readInputs(remappedJars.inputJar()); + remapper.apply(outputConsumer); + } catch (Exception e) { + throw new RuntimeException("Failed to remap JAR " + remappedJars.inputJar() + " with mappings from " + mappingsProvider.tinyMappings, e); + } finally { + remapper.finish(); + } + } + + protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) { + } + + private void cleanOutputs(List remappedJars) throws IOException { + for (RemappedJars remappedJar : remappedJars) { + Files.deleteIfExists(remappedJar.outputJar()); + } + } + + public Project getProject() { + return project; + } + + public M getMinecraftProvider() { + return minecraftProvider; + } + + public record RemappedJars(Path inputJar, Path outputJar, MappingsNamespace sourceNamespace, Path... remapClasspath) { + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/IntermediaryMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/IntermediaryMinecraftProvider.java new file mode 100644 index 00000000..11ce20bf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/IntermediaryMinecraftProvider.java @@ -0,0 +1,101 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft.mapped; + +import java.nio.file.Path; +import java.util.List; + +import org.gradle.api.Project; + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider; +import net.fabricmc.loom.util.SidedClassVisitor; +import net.fabricmc.tinyremapper.TinyRemapper; + +public abstract sealed class IntermediaryMinecraftProvider extends AbstractMappedMinecraftProvider permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.ServerOnlyImpl, IntermediaryMinecraftProvider.SplitImpl { + public IntermediaryMinecraftProvider(Project project, M minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + protected Path getDirectory() { + return extension.getMinecraftProvider().workingDir().toPath(); + } + + @Override + public final MappingsNamespace getTargetNamespace() { + return MappingsNamespace.INTERMEDIARY; + } + + public static final class MergedImpl extends IntermediaryMinecraftProvider implements Merged { + public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + public List getRemappedJars() { + return List.of( + new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), MappingsNamespace.OFFICIAL) + ); + } + } + + public static final class SplitImpl extends IntermediaryMinecraftProvider implements Split { + public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + public List getRemappedJars() { + return List.of( + new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), MappingsNamespace.OFFICIAL), + new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar()) + ); + } + + @Override + protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) { + if (remappedJars.outputJar().equals(getClientOnlyJar())) { + tinyRemapperBuilder.extraPostApplyVisitor(SidedClassVisitor.CLIENT); + } + } + } + + public static final class ServerOnlyImpl extends IntermediaryMinecraftProvider implements ServerOnly { + public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + public List getRemappedJars() { + return List.of( + new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL) + ); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/MappedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/MappedMinecraftProvider.java new file mode 100644 index 00000000..7bbd61be --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/MappedMinecraftProvider.java @@ -0,0 +1,80 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft.mapped; + +import java.nio.file.Path; +import java.util.List; + +public interface MappedMinecraftProvider { + List getMinecraftJars(); + + interface ProviderImpl extends MappedMinecraftProvider { + Path getJar(String name); + } + + interface Merged extends ProviderImpl { + String MERGED = "merged"; + + default Path getMergedJar() { + return getJar(MERGED); + } + + @Override + default List getMinecraftJars() { + return List.of(getMergedJar()); + } + } + + interface Split extends ProviderImpl { + String COMMON = "common"; + String CLIENT_ONLY = "clientOnly"; + + default Path getCommonJar() { + return getJar(COMMON); + } + + default Path getClientOnlyJar() { + return getJar(CLIENT_ONLY); + } + + @Override + default List getMinecraftJars() { + return List.of(getCommonJar(), getClientOnlyJar()); + } + } + + interface ServerOnly extends ProviderImpl { + String SERVER_ONLY = "serverOnly"; + + default Path getServerOnlyJar() { + return getJar(SERVER_ONLY); + } + + @Override + default List getMinecraftJars() { + return List.of(getServerOnlyJar()); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java new file mode 100644 index 00000000..a143ae60 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java @@ -0,0 +1,119 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft.mapped; + +import java.nio.file.Path; +import java.util.List; +import java.util.function.BiConsumer; + +import org.gradle.api.Project; + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.SidedClassVisitor; +import net.fabricmc.tinyremapper.TinyRemapper; + +public abstract class NamedMinecraftProvider extends AbstractMappedMinecraftProvider { + public NamedMinecraftProvider(Project project, M minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + protected Path getDirectory() { + return extension.getMappingsProvider().mappingsWorkingDir(); + } + + @Override + public final MappingsNamespace getTargetNamespace() { + return MappingsNamespace.NAMED; + } + + public static final class MergedImpl extends NamedMinecraftProvider implements Merged { + public MergedImpl(Project project, MergedMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + public List getRemappedJars() { + return List.of( + new RemappedJars(minecraftProvider.getMergedJar(), getMergedJar(), MappingsNamespace.OFFICIAL) + ); + } + + @Override + protected void applyDependencies(BiConsumer consumer) { + consumer.accept(Constants.Configurations.MINECRAFT_NAMED, MERGED); + } + } + + public static final class SplitImpl extends NamedMinecraftProvider implements Split { + public SplitImpl(Project project, SplitMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + public List getRemappedJars() { + return List.of( + new RemappedJars(minecraftProvider.getMinecraftCommonJar(), getCommonJar(), MappingsNamespace.OFFICIAL), + new RemappedJars(minecraftProvider.getMinecraftClientOnlyJar(), getClientOnlyJar(), MappingsNamespace.OFFICIAL, minecraftProvider.getMinecraftCommonJar()) + ); + } + + @Override + protected void configureRemapper(RemappedJars remappedJars, TinyRemapper.Builder tinyRemapperBuilder) { + if (remappedJars.outputJar().equals(getClientOnlyJar())) { + tinyRemapperBuilder.extraPostApplyVisitor(SidedClassVisitor.CLIENT); + } + } + + @Override + protected void applyDependencies(BiConsumer consumer) { + consumer.accept(Constants.Configurations.MINECRAFT_NAMED, COMMON); + consumer.accept(Constants.Configurations.MINECRAFT_NAMED, CLIENT_ONLY); + } + } + + public static final class ServerOnlyImpl extends NamedMinecraftProvider implements ServerOnly { + public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) { + super(project, minecraftProvider); + } + + @Override + public List getRemappedJars() { + return List.of( + new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL) + ); + } + + @Override + protected void applyDependencies(BiConsumer consumer) { + consumer.accept(Constants.Configurations.MINECRAFT_NAMED, SERVER_ONLY); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java new file mode 100644 index 00000000..de22cf0b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java @@ -0,0 +1,169 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.minecraft.mapped; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.configuration.processors.JarProcessorManager; +import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider; + +public abstract class ProcessedNamedMinecraftProvider> extends NamedMinecraftProvider { + private final P parentMinecraftProvider; + private final JarProcessorManager jarProcessorManager; + private final String projectMappedName; + private final Path projectMappedDir; + + public ProcessedNamedMinecraftProvider(P parentMinecraftProvide, JarProcessorManager jarProcessorManager) { + super(parentMinecraftProvide.getProject(), parentMinecraftProvide.getMinecraftProvider()); + this.parentMinecraftProvider = parentMinecraftProvide; + this.jarProcessorManager = jarProcessorManager; + + this.projectMappedName = "minecraft-project-%s-".formatted(getProject().getPath().replace(':', '@')); + + final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + this.projectMappedDir = extension.getFiles().getRootProjectPersistentCache().toPath() + .resolve(getMinecraftProvider().minecraftVersion()) + .resolve(extension.getMappingsProvider().mappingsIdentifier()); + } + + @Override + public void provide(boolean applyDependencies) throws Exception { + parentMinecraftProvider.provide(false); + + final List inputJars = parentMinecraftProvider.getMinecraftJars(); + boolean requiresProcessing = LoomGradlePlugin.refreshDeps || inputJars.stream() + .map(this::getProcessedPath) + .map(Path::toFile) + .anyMatch(jarProcessorManager::isInvalid); + + if (requiresProcessing) { + try { + Files.createDirectories(projectMappedDir); + } catch (IOException e) { + throw new UncheckedIOException("Failed to create project mapped dir", e); + } + + for (Path inputJar : inputJars) { + final Path outputJar = getProcessedPath(inputJar); + deleteSimilarJars(outputJar); + + Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING); + jarProcessorManager.process(outputJar.toFile()); + } + } + + if (applyDependencies) { + parentMinecraftProvider.applyDependencies((configuration, name) -> getProject().getDependencies().add(configuration, getDependencyNotation(name))); + } + } + + private void deleteSimilarJars(Path jar) throws IOException { + Files.deleteIfExists(jar); + + for (Path path : Files.list(jar.getParent()).filter(Files::isRegularFile) + .filter(path -> path.getFileName().startsWith(jar.getFileName().toString().replace(".jar", ""))).toList()) { + Files.deleteIfExists(path); + } + } + + @Override + protected String getName(String name) { + return "%s%s-%s".formatted(projectMappedName, name, getTargetNamespace().toString()); + } + + @Override + public Path getJar(String name) { + // Something has gone wrong if this gets called. + throw new UnsupportedOperationException(); + } + + @Override + public List getRemappedJars() { + throw new UnsupportedOperationException(); + } + + @Override + public List getMinecraftJars() { + return getParentMinecraftProvider().getMinecraftJars().stream() + .map(this::getProcessedPath) + .toList(); + } + + public P getParentMinecraftProvider() { + return parentMinecraftProvider; + } + + public Path getProcessedPath(Path input) { + return projectMappedDir.resolve(input.getFileName().toString().replace("minecraft-", projectMappedName)); + } + + public static final class MergedImpl extends ProcessedNamedMinecraftProvider implements Merged { + public MergedImpl(NamedMinecraftProvider.MergedImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) { + super(parentMinecraftProvide, jarProcessorManager); + } + + @Override + public Path getMergedJar() { + return getProcessedPath(getParentMinecraftProvider().getMergedJar()); + } + } + + public static final class SplitImpl extends ProcessedNamedMinecraftProvider implements Split { + public SplitImpl(NamedMinecraftProvider.SplitImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) { + super(parentMinecraftProvide, jarProcessorManager); + } + + @Override + public Path getCommonJar() { + return getProcessedPath(getParentMinecraftProvider().getCommonJar()); + } + + @Override + public Path getClientOnlyJar() { + return getProcessedPath(getParentMinecraftProvider().getClientOnlyJar()); + } + } + + public static final class ServerOnlyImpl extends ProcessedNamedMinecraftProvider implements ServerOnly { + public ServerOnlyImpl(NamedMinecraftProvider.ServerOnlyImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) { + super(parentMinecraftProvide, jarProcessorManager); + } + + @Override + public Path getServerOnlyJar() { + return getProcessedPath(getParentMinecraftProvider().getServerOnlyJar()); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java index 0c58e5c8..8bc63e20 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java +++ b/src/main/java/net/fabricmc/loom/decompilers/DecompilerConfiguration.java @@ -27,6 +27,7 @@ package net.fabricmc.loom.decompilers; import org.gradle.api.Project; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.decompilers.cfr.LoomCFRDecompiler; import net.fabricmc.loom.decompilers.fernflower.FabricFernFlowerDecompiler; @@ -35,8 +36,11 @@ public final class DecompilerConfiguration { } public static void setup(Project project) { - LoomGradleExtension extension = LoomGradleExtension.get(project); - extension.getGameDecompilers().add(new FabricFernFlowerDecompiler()); - extension.getGameDecompilers().add(new LoomCFRDecompiler()); + registerDecompiler(project, "fernFlower", FabricFernFlowerDecompiler.class); + registerDecompiler(project, "cfr", LoomCFRDecompiler.class); + } + + private static void registerDecompiler(Project project, String name, Class decompilerClass) { + LoomGradleExtension.get(project).getDecompilerOptions().register(name, options -> options.getDecompilerClassName().set(decompilerClass.getName())); } } diff --git a/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java index cb715139..faf53b1e 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java +++ b/src/main/java/net/fabricmc/loom/decompilers/cfr/LoomCFRDecompiler.java @@ -30,8 +30,6 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.jar.Attributes; @@ -48,20 +46,14 @@ import org.benf.cfr.reader.util.output.SinkDumperFactory; import net.fabricmc.loom.api.decompilers.DecompilationMetadata; import net.fabricmc.loom.api.decompilers.LoomDecompiler; -import net.fabricmc.loom.decompilers.LineNumberRemapper; -public class LoomCFRDecompiler implements LoomDecompiler { +public final class LoomCFRDecompiler implements LoomDecompiler { private static final Map DECOMPILE_OPTIONS = Map.of( "renameillegalidents", "true", "trackbytecodeloc", "true", "comments", "false" ); - @Override - public String name() { - return "Cfr"; - } - @Override public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) { final String path = compiledJar.toAbsolutePath().toString(); @@ -132,21 +124,4 @@ public class LoomCFRDecompiler implements LoomDecompiler { throw new UncheckedIOException("Failed to write line map", e); } } - - // A test main class to make it quicker/easier to debug with minimal jars - public static void main(String[] args) throws IOException { - LoomCFRDecompiler decompiler = new LoomCFRDecompiler(); - - Path lineMap = Paths.get("linemap.txt"); - - decompiler.decompile(Paths.get("input.jar"), - Paths.get("output-sources.jar"), - lineMap, - new DecompilationMetadata(4, null, Collections.emptyList(), null, Collections.emptyMap()) - ); - - LineNumberRemapper lineNumberRemapper = new LineNumberRemapper(); - lineNumberRemapper.readMappings(lineMap.toFile()); - lineNumberRemapper.process(null, Paths.get("input.jar"), Paths.get("output.jar")); - } } diff --git a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java b/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java index 9ad8111e..18d55451 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java +++ b/src/main/java/net/fabricmc/loom/decompilers/fernflower/FabricFernFlowerDecompiler.java @@ -37,11 +37,6 @@ import net.fabricmc.loom.api.decompilers.DecompilationMetadata; import net.fabricmc.loom.api.decompilers.LoomDecompiler; public final class FabricFernFlowerDecompiler implements LoomDecompiler { - @Override - public String name() { - return "FernFlower"; - } - @Override public void decompile(Path compiledJar, Path sourcesDestination, Path linemapDestination, DecompilationMetadata metaData) { final Map options = new HashMap<>( diff --git a/src/main/java/net/fabricmc/loom/extension/LoomFiles.java b/src/main/java/net/fabricmc/loom/extension/LoomFiles.java index 4fc07bed..6e5e0f12 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomFiles.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomFiles.java @@ -47,4 +47,5 @@ public interface LoomFiles { File getDefaultLog4jConfigFile(); File getDevLauncherConfig(); File getUnpickLoggingConfigFile(); + File getRemapClasspathFile(); } diff --git a/src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java index a79bb56f..16752f5d 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java @@ -92,4 +92,9 @@ public abstract class LoomFilesBaseImpl implements LoomFiles { public File getUnpickLoggingConfigFile() { return new File(getProjectPersistentCache(), "unpick-logging.properties"); } + + @Override + public File getRemapClasspathFile() { + return new File(getProjectPersistentCache(), "remapClasspath.txt"); + } } diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index 7279a70a..e87f760d 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -33,7 +33,6 @@ import java.util.function.Consumer; import com.google.common.base.Suppliers; import org.gradle.api.Action; -import org.gradle.api.DomainObjectCollection; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; @@ -47,7 +46,7 @@ import org.gradle.api.publish.maven.MavenPublication; import net.fabricmc.loom.api.ForgeExtensionAPI; import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.MixinExtensionAPI; -import net.fabricmc.loom.api.decompilers.LoomDecompiler; +import net.fabricmc.loom.api.decompilers.DecompilerOptions; import net.fabricmc.loom.api.decompilers.architectury.ArchitecturyLoomDecompiler; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; import net.fabricmc.loom.configuration.ide.RunConfig; @@ -59,6 +58,7 @@ import net.fabricmc.loom.configuration.providers.mappings.GradleMappingContext; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpec; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingSpecBuilderImpl; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; import net.fabricmc.loom.util.DeprecationHelper; import net.fabricmc.loom.util.ModPlatform; @@ -70,7 +70,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA private static final String PLATFORM_PROPERTY = "loom.platform"; protected final DeprecationHelper deprecationHelper; - protected final DomainObjectCollection decompilers; protected final ListProperty jarProcessors; protected final ConfigurableFileCollection log4jConfigs; protected final RegularFileProperty accessWidener; @@ -81,10 +80,12 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA protected final Property transitiveAccessWideners; protected final Property intermediary; protected final Property enableInterfaceInjection; + private final Property minecraftJarConfiguration; private final ModVersionParser versionParser; - private NamedDomainObjectContainer runConfigs; + private final NamedDomainObjectContainer runConfigs; + private final NamedDomainObjectContainer decompilers; // =================== // Architectury Loom @@ -98,9 +99,6 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA private NamedDomainObjectContainer launchConfigs; protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) { - this.runConfigs = project.container(RunConfigSettings.class, - baseName -> new RunConfigSettings(project, baseName)); - this.decompilers = project.getObjects().domainObjectSet(LoomDecompiler.class); this.jarProcessors = project.getObjects().listProperty(JarProcessor.class) .empty(); this.log4jConfigs = project.files(directories.getDefaultLog4jConfigFile()); @@ -124,6 +122,16 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA this.versionParser = new ModVersionParser(project); this.deprecationHelper = new DeprecationHelper.ProjectBased(project); + + this.runConfigs = project.container(RunConfigSettings.class, + baseName -> new RunConfigSettings(project, baseName)); + this.decompilers = project.getObjects().domainObjectContainer(DecompilerOptions.class); + + this.minecraftJarConfiguration = project.getObjects().property(MinecraftJarConfiguration.class).convention(MinecraftJarConfiguration.MERGED); + this.minecraftJarConfiguration.finalizeValueOnRead(); + + this.accessWidener.finalizeValueOnRead(); + this.getGameJarProcessors().finalizeValueOnRead(); this.platform = project.provider(Suppliers.memoize(() -> { Object platformProperty = project.findProperty(PLATFORM_PROPERTY); @@ -161,10 +169,15 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA } @Override - public DomainObjectCollection getGameDecompilers() { + public NamedDomainObjectContainer getDecompilerOptions() { return decompilers; } + @Override + public void decompilers(Action> action) { + action.execute(decompilers); + } + @Override public ListProperty getGameJarProcessors() { return jarProcessors; @@ -244,6 +257,11 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA net.fabricmc.loom.configuration.MavenPublication.excludePublication(publication); } + @Override + public Property getMinecraftJarConfiguration() { + return minecraftJarConfiguration; + } + @Override public void silentMojangMappingsLicense() { this.silentMojangMappingsLicense = true; diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java index ba3194d4..b059474f 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java @@ -24,7 +24,7 @@ package net.fabricmc.loom.extension; -import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -41,14 +41,18 @@ 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; import net.fabricmc.loom.api.ForgeExtensionAPI; import net.fabricmc.loom.configuration.InstallerData; import net.fabricmc.loom.configuration.LoomDependencyManager; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.processors.JarProcessorManager; +import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; +import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; import net.fabricmc.loom.util.ModPlatform; import net.fabricmc.loom.util.function.LazyBool; @@ -59,7 +63,6 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen private final ConfigurableFileCollection unmappedMods; private final Supplier forgeExtension; - private final ConfigurableFileCollection mixinMappings; private final MappingSet[] srcMappingCache = new MappingSet[2]; private final Mercury[] srcMercuryCache = new Mercury[2]; private final Map> lazyConfigurations = new HashMap<>(); @@ -67,6 +70,10 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen private LoomDependencyManager dependencyManager; private JarProcessorManager jarProcessorManager; + private MinecraftProvider minecraftProvider; + private MappingsProviderImpl mappingsProvider; + private NamedMinecraftProvider namedMinecraftProvider; + private IntermediaryMinecraftProvider intermediaryMinecraftProvider; private InstallerData installerData; // +-------------------+ @@ -80,7 +87,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(); this.forgeExtension = Suppliers.memoize(() -> isForge() ? project.getObjects().newInstance(ForgeExtensionImpl.class, project, this) : null); @@ -97,18 +103,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; @@ -129,6 +123,55 @@ public class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implemen return Objects.requireNonNull(jarProcessorManager, "Cannot get JarProcessorManager before it has been setup"); } + @Override + public MinecraftProvider getMinecraftProvider() { + return Objects.requireNonNull(minecraftProvider, "Cannot get MinecraftProvider before it has been setup"); + } + + @Override + public void setMinecraftProvider(MinecraftProvider minecraftProvider) { + this.minecraftProvider = minecraftProvider; + } + + @Override + public MappingsProviderImpl getMappingsProvider() { + return Objects.requireNonNull(mappingsProvider, "Cannot get MappingsProvider before it has been setup"); + } + + @Override + public void setMappingsProvider(MappingsProviderImpl mappingsProvider) { + this.mappingsProvider = mappingsProvider; + } + + @Override + public NamedMinecraftProvider getNamedMinecraftProvider() { + return Objects.requireNonNull(namedMinecraftProvider, "Cannot get NamedMinecraftProvider before it has been setup"); + } + + @Override + public IntermediaryMinecraftProvider getIntermediaryMinecraftProvider() { + return Objects.requireNonNull(intermediaryMinecraftProvider, "Cannot get IntermediaryMinecraftProvider before it has been setup"); + } + + @Override + public void setNamedMinecraftProvider(NamedMinecraftProvider namedMinecraftProvider) { + this.namedMinecraftProvider = namedMinecraftProvider; + } + + @Override + public void setIntermediaryMinecraftProvider(IntermediaryMinecraftProvider intermediaryMinecraftProvider) { + this.intermediaryMinecraftProvider = intermediaryMinecraftProvider; + } + + @Override + public FileCollection getMinecraftJarsCollection(MappingsNamespace mappingsNamespace) { + return getProject().files( + getProject().provider(() -> + getProject().files(getMinecraftJars(mappingsNamespace).stream().map(Path::toFile).toList()) + ) + ); + } + @Override public MappingSet getOrCreateSrcMappingCache(int id, Supplier factory) { if (id < 0 || id >= srcMappingCache.length) return factory.get(); diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 4c3069aa..9c493012 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -43,13 +43,9 @@ import javax.inject.Inject; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; -import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; -import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.TaskAction; import org.gradle.workers.WorkAction; import org.gradle.workers.WorkParameters; @@ -58,12 +54,11 @@ import org.gradle.workers.WorkerExecutor; import org.gradle.workers.internal.WorkerDaemonClientsManager; import org.jetbrains.annotations.Nullable; -import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.decompilers.DecompilationMetadata; +import net.fabricmc.loom.api.decompilers.DecompilerOptions; import net.fabricmc.loom.api.decompilers.LoomDecompiler; import net.fabricmc.loom.configuration.accesswidener.AccessWidenerFile; import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerMappingsProcessor; -import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.decompilers.LineNumberRemapper; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.FileSystemUtil; @@ -76,22 +71,19 @@ import net.fabricmc.loom.util.ipc.IPCClient; import net.fabricmc.loom.util.ipc.IPCServer; public abstract class GenerateSourcesTask extends AbstractLoomTask { - public final LoomDecompiler decompiler; + private final DecompilerOptions decompilerOptions; + /** + * The jar to decompile, can be the unpick jar. + */ @InputFile public abstract RegularFileProperty getInputJar(); /** - * Max memory for forked JVM in megabytes. + * The jar used at runtime. */ - @Input - public abstract Property getMaxMemory(); - - @Input - public abstract MapProperty getOptions(); - - @InputFiles - public abstract ConfigurableFileCollection getClasspath(); + @InputFile + public abstract RegularFileProperty getRuntimeJar(); @Inject public abstract WorkerExecutor getWorkerExecutor(); @@ -100,21 +92,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { public abstract WorkerDaemonClientsManager getWorkerDaemonClientsManager(); @Inject - public GenerateSourcesTask(LoomDecompiler decompiler) { - this.decompiler = decompiler; - - Objects.requireNonNull(getDecompilerConstructor(this.decompiler.getClass().getCanonicalName()), - "%s must have a no args constructor".formatted(this.decompiler.getClass().getCanonicalName())); - - FileCollection decompilerClasspath = decompiler.getBootstrapClasspath(getProject()); - - if (decompilerClasspath != null) { - getClasspath().from(decompilerClasspath); - } + public GenerateSourcesTask(DecompilerOptions decompilerOptions) { + this.decompilerOptions = decompilerOptions; getOutputs().upToDateWhen((o) -> false); - getMaxMemory().convention(4096L).finalizeValueOnRead(); - getOptions().finalizeValueOnRead(); } @TaskAction @@ -134,9 +115,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { final Path ipcPath = Files.createTempFile("loom", "ipc"); Files.deleteIfExists(ipcPath); - try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompiler.name(), "Decompiling minecraft sources"); + try (ThreadedProgressLoggerConsumer loggerConsumer = new ThreadedProgressLoggerConsumer(getProject(), decompilerOptions.getName(), "Decompiling minecraft sources"); IPCServer logReceiver = new IPCServer(ipcPath, loggerConsumer)) { - doWork(ipcPath); + doWork(logReceiver); } catch (InterruptedException e) { throw new RuntimeException("Failed to shutdown log receiver", e); } finally { @@ -144,24 +125,22 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - private void doWork(@Nullable Path ipcPath) { + private void doWork(@Nullable IPCServer ipcServer) { final String jvmMarkerValue = UUID.randomUUID().toString(); final WorkQueue workQueue = createWorkQueue(jvmMarkerValue); workQueue.submit(DecompileAction.class, params -> { - params.getDecompilerClass().set(decompiler.getClass().getCanonicalName()); - - params.getOptions().set(getOptions()); + params.getDecompilerOptions().set(decompilerOptions.toDto()); params.getInputJar().set(getInputJar()); - params.getRuntimeJar().set(getExtension().getMappingsProvider().mappedProvider.getMappedJar()); + params.getRuntimeJar().set(getRuntimeJar()); params.getSourcesDestinationJar().set(getMappedJarFileWithSuffix("-sources.jar")); params.getLinemap().set(getMappedJarFileWithSuffix("-sources.lmap")); params.getLinemapJar().set(getMappedJarFileWithSuffix("-linemapped.jar")); params.getMappings().set(getMappings(getProject(), getExtension()).toFile()); - if (ipcPath != null) { - params.getIPCPath().set(ipcPath.toFile()); + if (ipcServer != null) { + params.getIPCPath().set(ipcServer.getPath().toFile()); } params.getClassPath().setFrom(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES)); @@ -170,10 +149,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { try { workQueue.await(); } finally { - if (useProcessIsolation()) { + if (ipcServer != null) { boolean stopped = WorkerDaemonClientsManagerHelper.stopIdleJVM(getWorkerDaemonClientsManager(), jvmMarkerValue); - if (!stopped) { + if (!stopped && ipcServer.hasReceivedMessage()) { throw new RuntimeException("Failed to stop decompile worker JVM"); } } @@ -187,9 +166,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { return getWorkerExecutor().processIsolation(spec -> { spec.forkOptions(forkOptions -> { - forkOptions.setMaxHeapSize("%dm".formatted(getMaxMemory().get())); + forkOptions.setMaxHeapSize("%dm".formatted(decompilerOptions.getMemory().get())); forkOptions.systemProperty(WorkerDaemonClientsManagerHelper.MARKER_PROP, jvmMarkerValue); - forkOptions.bootstrapClasspath(getClasspath()); + forkOptions.bootstrapClasspath(decompilerOptions.getClasspath()); }); }); } @@ -200,9 +179,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } public interface DecompileParams extends WorkParameters { - Property getDecompilerClass(); - - MapProperty getOptions(); + Property getDecompilerOptions(); RegularFileProperty getInputJar(); RegularFileProperty getRuntimeJar(); @@ -241,20 +218,26 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { final Path linemapJar = getParameters().getLinemapJar().get().getAsFile().toPath(); final Path runtimeJar = getParameters().getRuntimeJar().get().getAsFile().toPath(); + final DecompilerOptions.Dto decompilerOptions = getParameters().getDecompilerOptions().get(); + final LoomDecompiler decompiler; try { - decompiler = getDecompilerConstructor(getParameters().getDecompilerClass().get()).newInstance(); + final String className = decompilerOptions.className(); + final Constructor decompilerConstructor = getDecompilerConstructor(className); + Objects.requireNonNull(decompilerConstructor, "%s must have a no args constructor".formatted(className)); + + decompiler = decompilerConstructor.newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Failed to create decompiler", e); } DecompilationMetadata metadata = new DecompilationMetadata( - Runtime.getRuntime().availableProcessors(), + decompilerOptions.maxThreads(), getParameters().getMappings().get().getAsFile().toPath(), getLibraries(), logger, - getParameters().getOptions().get() + decompilerOptions.options() ); decompiler.decompile( @@ -304,18 +287,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } private File getMappedJarFileWithSuffix(String suffix) { - return getMappedJarFileWithSuffix(getProject(), suffix); - } - - public static File getMappedJarFileWithSuffix(Project project, String suffix) { - return getMappedJarFileWithSuffix(project, suffix, false); - } - - public static File getMappedJarFileWithSuffix(Project project, String suffix, boolean forgeJar) { - LoomGradleExtension extension = LoomGradleExtension.get(project); - MappingsProviderImpl mappingsProvider = extension.getMappingsProvider(); - File mappedJar = forgeJar ? mappingsProvider.mappedProvider.getForgeMappedJar() : mappingsProvider.mappedProvider.getMappedJar(); - String path = mappedJar.getAbsolutePath(); + String path = getRuntimeJar().get().getAsFile().getAbsolutePath(); if (!path.toLowerCase(Locale.ROOT).endsWith(".jar")) { throw new RuntimeException("Invalid mapped JAR path: " + path); diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 23c4b9b9..09b36520 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -26,11 +26,17 @@ package net.fabricmc.loom.task; import com.google.common.base.Preconditions; import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarConfiguration; +import net.fabricmc.loom.task.launch.GenerateDLIConfigTask; +import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask; +import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask; import net.fabricmc.loom.util.Constants; public final class LoomTasks { @@ -47,11 +53,39 @@ public final class LoomTasks { RemapTaskConfiguration.setupRemap(project); - TaskProvider extractNatives = tasks.register("extractNatives", ExtractNativesTask.class); + tasks.register("extractNatives", ExtractNativesTask.class, t -> { + t.setDescription("Extracts the minecraft platform specific natives."); + }); tasks.register("downloadAssets", DownloadAssetsTask.class, t -> { - t.dependsOn(extractNatives); t.setDescription("Downloads required assets for Fabric."); }); + tasks.register("generateDLIConfig", GenerateDLIConfigTask.class, t -> { + t.setDescription("Generate the DevLaunchInjector config file"); + }); + tasks.register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> { + t.setDescription("Generate the log4j config file"); + }); + tasks.register("generateRemapClasspath", GenerateRemapClasspathTask.class, t -> { + t.setDescription("Generate the remap classpath file"); + }); + + tasks.register("configureLaunch", task -> { + task.dependsOn(tasks.named("generateDLIConfig")); + task.dependsOn(tasks.named("generateLog4jConfig")); + task.dependsOn(tasks.named("generateRemapClasspath")); + + task.setDescription("Setup the required files to launch Minecraft"); + task.setGroup(Constants.TaskGroup.FABRIC); + }); + + tasks.register("configureClientLaunch", task -> { + task.dependsOn(tasks.named("extractNatives")); + task.dependsOn(tasks.named("downloadAssets")); + task.dependsOn(tasks.named("configureLaunch")); + + task.setDescription("Setup the required files to launch the Minecraft client"); + task.setGroup(Constants.TaskGroup.FABRIC); + }); TaskProvider validateAccessWidener = tasks.register("validateAccessWidener", ValidateAccessWidenerTask.class, t -> { t.setDescription("Validate all the rules in the access widener against the Minecraft jar"); @@ -62,20 +96,18 @@ public final class LoomTasks { registerIDETasks(tasks); registerRunTasks(tasks, project); - registerLaunchSettings(project); - registerDecompileTasks(tasks, project); } private static void registerIDETasks(TaskContainer tasks) { tasks.register("genIdeaWorkspace", GenIdeaProjectTask.class, t -> { t.setDescription("Generates an IntelliJ IDEA workspace from this project."); - t.dependsOn("idea", "downloadAssets"); + t.dependsOn("idea", getIDELaunchConfigureTaskName(t.getProject())); t.setGroup(Constants.TaskGroup.IDE); }); tasks.register("genEclipseRuns", GenEclipseRunsTask.class, t -> { t.setDescription("Generates Eclipse run configurations for this project."); - t.dependsOn("downloadAssets"); + t.dependsOn(getIDELaunchConfigureTaskName(t.getProject())); t.setGroup(Constants.TaskGroup.IDE); }); @@ -86,7 +118,7 @@ public final class LoomTasks { tasks.register("vscode", GenVsCodeProjectTask.class, t -> { t.setDescription("Generates VSCode launch configurations."); - t.dependsOn("downloadAssets"); + t.dependsOn(getIDELaunchConfigureTaskName(t.getProject())); t.setGroup(Constants.TaskGroup.IDE); }); } @@ -103,52 +135,25 @@ public final class LoomTasks { tasks.register(taskName, RunGameTask.class, config).configure(t -> { t.setDescription("Starts the '" + config.getConfigName() + "' run configuration"); - if (config.getEnvironment().equals("client")) { - t.dependsOn("downloadAssets"); - } + t.dependsOn(config.getEnvironment().equals("client") ? "configureClientLaunch" : "configureLaunch"); }); }); - extension.getRunConfigs().create("client", RunConfigSettings::client); extension.getRunConfigs().create("server", RunConfigSettings::server); + + // Remove the client run config when server only. Done by name to not remove any possible custom run configs + project.afterEvaluate(p -> { + if (extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY) { + extension.getRunConfigs().removeIf(settings -> settings.getName().equals("client")); + } + }); } - private static void registerLaunchSettings(Project project) { - LoomGradleExtension extension = LoomGradleExtension.get(project); - Preconditions.checkArgument(extension.getLaunchConfigs().size() == 0, "Launch configurations must not be registered before loom"); - extension.getLaunchConfigs().create("client"); - extension.getLaunchConfigs().create("server"); - - if (extension.isForge()) { - extension.getLaunchConfigs().create("data"); - } - } - - private static void registerDecompileTasks(TaskContainer tasks, Project project) { - LoomGradleExtension.get(project).getGameDecompilers().configureEach(decompiler -> { - String taskName = "genSourcesWith" + decompiler.name(); - // Decompiler will be passed to the constructor of GenerateSourcesTask - tasks.register(taskName, GenerateSourcesTask.class, decompiler).configure(task -> { - task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name())); - task.setGroup(Constants.TaskGroup.FABRIC); - task.dependsOn(tasks.named("validateAccessWidener")); - }); - }); - - LoomGradleExtension.get(project).getArchGameDecompilers().configureEach(decompiler -> { - String taskName = "genSourcesWith" + decompiler.name(); - // Decompiler will be passed to the constructor of ArchitecturyGenerateSourcesTask - tasks.register(taskName, ArchitecturyGenerateSourcesTask.class, decompiler).configure(task -> { - task.setDescription("Decompile minecraft using %s.".formatted(decompiler.name())); - task.setGroup(Constants.TaskGroup.FABRIC); - }); - }); - - tasks.register("genSources", task -> { - task.setDescription("Decompile minecraft using the default decompiler."); - task.setGroup(Constants.TaskGroup.FABRIC); - - task.dependsOn(project.getTasks().named("genSourcesWithCfr")); + public static Provider getIDELaunchConfigureTaskName(Project project) { + return project.provider(() -> { + final MinecraftJarConfiguration jarConfiguration = LoomGradleExtension.get(project).getMinecraftJarConfiguration().get(); + final String name = jarConfiguration == MinecraftJarConfiguration.SERVER_ONLY ? "configureLaunch" : "configureClientLaunch"; + return project.getTasks().getByName(name); }); } } diff --git a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java index 5a2444e1..310aa6c7 100644 --- a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java +++ b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java @@ -51,7 +51,6 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.api.mappings.layered.spec.LayeredMappingSpecBuilder; import net.fabricmc.loom.configuration.providers.mappings.LayeredMappingsDependency; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.util.SourceRemapper; import net.fabricmc.lorenztiny.TinyMappingsJoiner; import net.fabricmc.mappingio.MappingReader; @@ -101,7 +100,7 @@ public class MigrateMappingsTask extends AbstractLoomTask { try { MemoryMappingTree currentMappings = mappingsProvider.getMappings(); MemoryMappingTree targetMappings = getMappings(mappings); - migrateMappings(project, extension, extension.getMinecraftMappedProvider(), inputDir, outputDir, currentMappings, targetMappings); + migrateMappings(project, extension, extension, inputDir, outputDir, currentMappings, targetMappings); project.getLogger().lifecycle(":remapped project written to " + outputDir.toAbsolutePath()); } catch (IOException e) { throw new IllegalArgumentException("Error while loading mappings", e); @@ -157,8 +156,8 @@ public class MigrateMappingsTask extends AbstractLoomTask { return mappingTree; } - private static void migrateMappings(Project project, LoomGradleExtension extension, MinecraftMappedProvider minecraftMappedProvider, - Path inputDir, Path outputDir, MemoryMappingTree currentMappings, MemoryMappingTree targetMappings + private static void migrateMappings(Project project, LoomGradleExtension extension, + Path inputDir, Path outputDir, MemoryMappingTree currentMappings, MemoryMappingTree targetMappings ) throws IOException { project.getLogger().info(":joining mappings"); @@ -174,8 +173,13 @@ public class MigrateMappingsTask extends AbstractLoomTask { final JavaVersion javaVersion = project.getExtensions().getByType(JavaPluginExtension.class).getSourceCompatibility(); mercury.setSourceCompatibility(javaVersion.toString()); - mercury.getClassPath().add(minecraftMappedProvider.getMappedJar().toPath()); - mercury.getClassPath().add(minecraftMappedProvider.getIntermediaryJar().toPath()); + for (Path intermediaryJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) { + mercury.getClassPath().add(intermediaryJar); + } + + for (Path intermediaryJar : extension.getMinecraftJars(MappingsNamespace.NAMED)) { + mercury.getClassPath().add(intermediaryJar); + } if (extension.isForge()) { mercury.getClassPath().add(minecraftMappedProvider.getSrgJar().toPath()); 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..f5761d0a --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java @@ -0,0 +1,106 @@ +/* + * 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.getInputFile().set(getInputFile()); + }); + } + + public interface ReadInputsParams extends WorkParameters { + Property getTinyRemapperBuildServiceUuid(); + 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.getOrCreateTag(inputFile), inputFile); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index 2a7415f8..fcaa8e49 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -27,7 +27,6 @@ package net.fabricmc.loom.task; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.io.Serializable; import java.io.Writer; @@ -41,6 +40,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; import java.util.Set; import java.util.jar.Manifest; import java.util.stream.Collectors; @@ -48,6 +48,7 @@ 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 dev.architectury.tinyremapper.InputTag; import dev.architectury.tinyremapper.OutputConsumerPath; @@ -66,9 +67,9 @@ import org.gradle.api.provider.Provider; import org.gradle.api.provider.SetProperty; 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; @@ -76,20 +77,22 @@ 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.FileSystemUtil; import net.fabricmc.loom.util.LfWriter; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.aw2at.Aw2At; import net.fabricmc.lorenztiny.TinyMappingsReader; +import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper; +import net.fabricmc.tinyremapper.OutputConsumerPath; +import net.fabricmc.tinyremapper.TinyRemapper; public abstract class RemapJarTask extends AbstractRemapJarTask { private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; @@ -111,6 +114,8 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { @Input public abstract SetProperty getAtAccessWideners(); + private Supplier tinyRemapperService = Suppliers.memoize(() -> TinyRemapperService.getOrCreate(this)); + @Inject public RemapJarTask() { super(); @@ -120,6 +125,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 @@ -132,14 +156,13 @@ 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())); final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get(); params.getUseMixinExtension().set(!legacyMixin); if (legacyMixin) { - params.getMixinMappings().from(extension.getAllMixinMappings()); setupLegacyMixinRefmapRemapping(params); } else if (extension.isForge()) { throw new RuntimeException("Forge must have useLegacyMixinAp enabled"); @@ -209,9 +232,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { public interface RemapParams extends AbstractRemapParams { ConfigurableFileCollection getNestedJars(); ConfigurableFileCollection getRemapClasspath(); - ConfigurableFileCollection getMixinMappings(); - - ListProperty> getMappings(); Property getForge(); @@ -223,19 +243,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(); @@ -248,9 +274,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { rewriteJar(); - tinyRemapper.finish(); - tinyRemapper = null; - LOGGER.debug("Finished remapping {}", inputFile); } catch (Exception e) { try { @@ -264,13 +287,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.getOrCreateTag(inputFile)); } } @@ -281,7 +300,7 @@ 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); @@ -343,15 +362,15 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { } } - 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); @@ -400,30 +419,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { }))); } } + } - private TinyRemapper createTinyRemapper() { - TinyRemapper.Builder builder = TinyRemapper.newRemapper(); - - 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 dev.architectury.tinyremapper.extension.mixin.MixinExtension()); - } - - TinyRemapper remapper = builder.build(); - - // Apply classpath - for (File file : getParameters().getRemapClasspath()) { - remapper.readClassPathAsync(file.toPath()); - } - - return remapper; - } + @Internal + public TinyRemapperService getTinyRemapperService() { + return tinyRemapperService.get(); } } 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 35fa28d3..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); @@ -84,8 +84,8 @@ public class RemapTaskConfiguration { // Remove -dev jars from the default jar task for (String configurationName : new String[] { JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME }) { Configuration configuration = project.getConfigurations().getByName(configurationName); + final Task jarTask = project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME); configuration.getArtifacts().removeIf(artifact -> { - Task jarTask = project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME); // if the artifact is a -dev jar and "builtBy jar" return "dev".equals(artifact.getClassifier()) && artifact.getBuildDependencies().getDependencies(null).contains(jarTask); }); diff --git a/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java b/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java index e750cb10..3ac72fec 100644 --- a/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/UnpickJarTask.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.Path; import javax.inject.Inject; @@ -40,7 +41,7 @@ import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.OutputFile; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.configuration.providers.LaunchProvider; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.extension.LoomFiles; import net.fabricmc.loom.util.Constants; @@ -76,7 +77,9 @@ public abstract class UnpickJarTask extends JavaExec { fileArg(getConstantJar().getSingleFile()); // Classpath - fileArg(getExtension().getMinecraftMappedProvider().getMappedJar()); + for (Path minecraftJar : getExtension().getMinecraftJars(MappingsNamespace.NAMED)) { + fileArg(minecraftJar.toFile()); + } for (File file : getUnpickClasspath()) { fileArg(file); @@ -89,7 +92,7 @@ public abstract class UnpickJarTask extends JavaExec { } private void writeUnpickLogConfig() { - try (InputStream is = LaunchProvider.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) { + try (InputStream is = UnpickJarTask.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) { Files.deleteIfExists(getDirectories().getUnpickLoggingConfigFile().toPath()); Files.copy(is, getDirectories().getUnpickLoggingConfigFile().toPath()); } catch (IOException e) { diff --git a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java index 485c9b3d..5f2e79b9 100644 --- a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java +++ b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.task; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; @@ -32,11 +33,11 @@ import java.nio.file.Files; import javax.inject.Inject; -import dev.architectury.tinyremapper.TinyRemapper; -import dev.architectury.tinyremapper.api.TrEnvironment; import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; @@ -44,21 +45,24 @@ import net.fabricmc.accesswidener.AccessWidenerFormatException; import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerVisitor; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.tinyremapper.TinyRemapper; +import net.fabricmc.tinyremapper.api.TrEnvironment; public abstract class ValidateAccessWidenerTask extends DefaultTask { @SkipWhenEmpty @InputFile public abstract RegularFileProperty getAccessWidener(); - @InputFile - public abstract RegularFileProperty getTargetJar(); + @InputFiles + public abstract ConfigurableFileCollection getTargetJars(); @Inject public ValidateAccessWidenerTask() { final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); getAccessWidener().convention(extension.getAccessWidenerPath()).finalizeValueOnRead(); - getTargetJar().convention(getProject().getObjects().fileProperty().fileValue(extension.getMinecraftMappedProvider().getMappedJar())).finalizeValueOnRead(); + getTargetJars().from(extension.getMinecraftJarsCollection(MappingsNamespace.NAMED)); // Ignore outputs for up-to-date checks as there aren't any (so only inputs are checked) getOutputs().upToDateWhen(task -> true); @@ -67,7 +71,10 @@ public abstract class ValidateAccessWidenerTask extends DefaultTask { @TaskAction public void run() { final TinyRemapper tinyRemapper = TinyRemapper.newRemapper().build(); - tinyRemapper.readClassPath(getTargetJar().get().getAsFile().toPath()); + + for (File file : getTargetJars().getFiles()) { + tinyRemapper.readClassPath(file.toPath()); + } final AccessWidenerValidator validator = new AccessWidenerValidator(tinyRemapper.getEnvironment()); final AccessWidenerReader accessWidenerReader = new AccessWidenerReader(validator); diff --git a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java new file mode 100644 index 00000000..f3259579 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java @@ -0,0 +1,119 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task.launch; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.gradle.api.logging.configuration.ConsoleOutput; +import org.gradle.api.tasks.TaskAction; + +import net.fabricmc.loom.task.AbstractLoomTask; + +public abstract class GenerateDLIConfigTask extends AbstractLoomTask { + @TaskAction + public void run() throws IOException { + final String nativesPath = getExtension().getFiles().getNativesDirectory(getProject()).getAbsolutePath(); + + final LaunchConfig launchConfig = new LaunchConfig() + .property("fabric.development", "true") + .property("fabric.remapClasspathFile", getExtension().getFiles().getRemapClasspathFile().getAbsolutePath()) + .property("log4j.configurationFile", getAllLog4JConfigFiles()) + .property("log4j2.formatMsgNoLookups", "true") + + .property("client", "java.library.path", nativesPath) + .property("client", "org.lwjgl.librarypath", nativesPath) + + .argument("client", "--assetIndex") + .argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion())) + .argument("client", "--assetsDir") + .argument("client", new File(getExtension().getFiles().getUserCache(), "assets").getAbsolutePath()); + + final boolean plainConsole = getProject().getGradle().getStartParameter().getConsoleOutput() == ConsoleOutput.Plain; + final boolean ansiSupportedIDE = new File(getProject().getRootDir(), ".vscode").exists() + || new File(getProject().getRootDir(), ".idea").exists() + || (Arrays.stream(getProject().getRootDir().listFiles()).anyMatch(file -> file.getName().endsWith(".iws"))); + + //Enable ansi by default for idea and vscode when gradle is not ran with plain console. + if (ansiSupportedIDE && !plainConsole) { + launchConfig.property("fabric.log.disableAnsi", "false"); + } + + FileUtils.writeStringToFile(getExtension().getFiles().getDevLauncherConfig(), launchConfig.asString(), StandardCharsets.UTF_8); + } + + private String getAllLog4JConfigFiles() { + return getExtension().getLog4jConfigs().getFiles().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(",")); + } + + public static class LaunchConfig { + private final Map> values = new HashMap<>(); + + public LaunchConfig property(String key, String value) { + return property("common", key, value); + } + + public LaunchConfig property(String side, String key, String value) { + values.computeIfAbsent(side + "Properties", (s -> new ArrayList<>())) + .add(String.format("%s=%s", key, value)); + return this; + } + + public LaunchConfig argument(String value) { + return argument("common", value); + } + + public LaunchConfig argument(String side, String value) { + values.computeIfAbsent(side + "Args", (s -> new ArrayList<>())) + .add(value); + return this; + } + + public String asString() { + StringJoiner stringJoiner = new StringJoiner("\n"); + + for (Map.Entry> entry : values.entrySet()) { + stringJoiner.add(entry.getKey()); + + for (String s : entry.getValue()) { + stringJoiner.add("\t" + s); + } + } + + return stringJoiner.toString(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/launch/GenerateLog4jConfigTask.java b/src/main/java/net/fabricmc/loom/task/launch/GenerateLog4jConfigTask.java new file mode 100644 index 00000000..fc249bba --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/launch/GenerateLog4jConfigTask.java @@ -0,0 +1,48 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task.launch; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.gradle.api.tasks.TaskAction; + +import net.fabricmc.loom.task.AbstractLoomTask; + +public abstract class GenerateLog4jConfigTask extends AbstractLoomTask { + @TaskAction + public void run() { + Path outputFile = getExtension().getFiles().getDefaultLog4jConfigFile().toPath(); + + try (InputStream is = GenerateLog4jConfigTask.class.getClassLoader().getResourceAsStream("log4j2.fabric.xml")) { + Files.deleteIfExists(outputFile); + Files.copy(is, outputFile); + } catch (IOException e) { + throw new RuntimeException("Failed to generate log4j config", e); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/launch/GenerateRemapClasspathTask.java b/src/main/java/net/fabricmc/loom/task/launch/GenerateRemapClasspathTask.java new file mode 100644 index 00000000..d97b5ab9 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/launch/GenerateRemapClasspathTask.java @@ -0,0 +1,69 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.task.launch; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.gradle.api.tasks.TaskAction; + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.RemappedConfigurationEntry; +import net.fabricmc.loom.task.AbstractLoomTask; +import net.fabricmc.loom.util.Constants; + +public abstract class GenerateRemapClasspathTask extends AbstractLoomTask { + @TaskAction + public void run() { + List inputConfigurations = new ArrayList<>(); + inputConfigurations.add(Constants.Configurations.LOADER_DEPENDENCIES); + inputConfigurations.addAll(Constants.MOD_COMPILE_ENTRIES.stream().map(RemappedConfigurationEntry::sourceConfiguration).toList()); + + List remapClasspath = new ArrayList<>(); + + for (String inputConfiguration : inputConfigurations) { + remapClasspath.addAll(getProject().getConfigurations().getByName(inputConfiguration).getFiles()); + } + + for (Path minecraftJar : getExtension().getMinecraftJars(MappingsNamespace.INTERMEDIARY)) { + remapClasspath.add(minecraftJar.toFile()); + } + + String str = remapClasspath.stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)); + + try { + Files.writeString(getExtension().getFiles().getRemapClasspathFile().toPath(), str); + } catch (IOException e) { + throw new RuntimeException("Failed to generate remap classpath", e); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java b/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java index 47175ba0..f43c9f95 100644 --- a/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java +++ b/src/main/java/net/fabricmc/loom/task/service/JarManifestService.java @@ -53,7 +53,7 @@ public abstract class JarManifestService implements BuildService getMixinVersion(); } - public static Provider get(Project project) { + public static synchronized Provider get(Project project) { return project.getGradle().getSharedServices().registerIfAbsent("LoomJarManifestService:" + project.getName(), JarManifestService.class, spec -> { spec.parameters(params -> { LoomGradleExtension extension = LoomGradleExtension.get(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 fe96fd54..537291dd 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 dev.architectury.tinyremapper.IMappingProvider; 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; -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, (from.equals("srg") || to.equals("srg")) && LoomGradleExtension.get(project).shouldGenerateSrgTiny() ? mappingsProvider.tinyMappingsWithSrg.toFile() : mappingsProvider.tinyMappings.toFile(), from, to, false); + return MappingsService.create(project, name, (from.equals("srg") || to.equals("srg")) && LoomGradleExtension.get(project).shouldGenerateSrgTiny() ? mappingsProvider.tinyMappingsWithSrg.toFile() : 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 synchronized 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..76dec9c3 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,32 @@ 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); + 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..9e05d656 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -0,0 +1,147 @@ +/* + * 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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.gradle.api.Project; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.kotlin.remapping.KotlinMetadataTinyRemapperExtension; +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(); + final boolean useKotlinExtension = project.getPluginManager().hasPlugin("org.jetbrains.kotlin.jvm"); + + // 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() + (useKotlinExtension ? ":kotlin" : ""); + + 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, useKotlinExtension); + }); + + service.readClasspath(remapJarTask.getClasspath().getFiles().stream().map(File::toPath).toList()); + + return service; + } + + private TinyRemapper tinyRemapper; + private final Map inputTagMap = new HashMap<>(); + 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, boolean useKotlinExtension) { + TinyRemapper.Builder builder = TinyRemapper.newRemapper(); + + for (IMappingProvider provider : mappings) { + builder.withMappings(provider); + } + + if (useMixinExtension) { + builder.extension(new net.fabricmc.tinyremapper.extension.mixin.MixinExtension()); + } + + if (useKotlinExtension) { + builder.extension(KotlinMetadataTinyRemapperExtension.INSTANCE); + } + + tinyRemapper = builder.build(); + } + + public synchronized InputTag getOrCreateTag(Path file) { + InputTag tag = inputTagMap.get(file.toAbsolutePath().toString()); + + if (tag == null) { + tag = tinyRemapper.createInputTag(); + inputTagMap.put(file.toAbsolutePath().toString(), tag); + } + + return tag; + } + + 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/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java index ba9cd2e6..06a73a15 100644 --- a/src/main/java/net/fabricmc/loom/util/Checksum.java +++ b/src/main/java/net/fabricmc/loom/util/Checksum.java @@ -31,6 +31,7 @@ import java.nio.charset.StandardCharsets; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; import com.google.common.io.Files; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; @@ -77,4 +78,8 @@ public class Checksum { HashCode hash = Hashing.sha256().hashString(string, StandardCharsets.UTF_8); return hash.asBytes(); } + + public static String toHex(byte[] bytes) { + return BaseEncoding.base16().lowerCase().encode(bytes); + } } diff --git a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java index 56ccf8d3..90303d9b 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/HashedDownloadUtil.java b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java index a9b77ab4..a5eb9c24 100644 --- a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java +++ b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java @@ -67,7 +67,7 @@ public class HashedDownloadUtil { } public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, boolean strict, Runnable startDownload) throws IOException { - if (LoomGradlePlugin.refreshDeps) { + if (LoomGradlePlugin.refreshDeps && !Boolean.getBoolean("loom.refresh")) { delete(to); } diff --git a/src/main/java/net/fabricmc/loom/util/SidedClassVisitor.java b/src/main/java/net/fabricmc/loom/util/SidedClassVisitor.java new file mode 100644 index 00000000..51fe9c51 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/SidedClassVisitor.java @@ -0,0 +1,59 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import java.util.Locale; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassVisitor; + +import net.fabricmc.tinyremapper.TinyRemapper; + +/** + * Applies the @Environment annotation to all classes. + */ +public final class SidedClassVisitor extends ClassVisitor { + public static final TinyRemapper.ApplyVisitorProvider CLIENT = (cls, next) -> new SidedClassVisitor("client", next); + public static final TinyRemapper.ApplyVisitorProvider SERVER = (cls, next) -> new SidedClassVisitor("server", next); + + private static final String ENVIRONMENT_DESCRIPTOR = "Lnet/fabricmc/api/Environment;"; + private static final String SIDE_DESCRIPTOR = "Lnet/fabricmc/api/EnvType;"; + + private final String side; + + private SidedClassVisitor(String side, ClassVisitor next) { + super(Constants.ASM_VERSION, next); + this.side = side; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + + final AnnotationVisitor annotationVisitor = visitAnnotation(ENVIRONMENT_DESCRIPTOR, true); + annotationVisitor.visitEnum("value", SIDE_DESCRIPTOR, side.toUpperCase(Locale.ROOT)); + annotationVisitor.visitEnd(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index 41af2c55..998f190d 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -202,8 +202,13 @@ public class SourceRemapper { } } - m.getClassPath().add(extension.getMinecraftMappedProvider().getMappedJar().toPath()); - m.getClassPath().add(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath()); + for (Path intermediaryJar : extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)) { + m.getClassPath().add(intermediaryJar); + } + + for (Path intermediaryJar : extension.getMinecraftJars(MappingsNamespace.NAMED)) { + m.getClassPath().add(intermediaryJar); + } if (extension.isForge()) { m.getClassPath().add(extension.getMinecraftMappedProvider().getSrgJar().toPath()); diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index e1a179cc..c79d568e 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -147,21 +147,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; @@ -173,7 +183,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/ipc/IPCServer.java b/src/main/java/net/fabricmc/loom/util/ipc/IPCServer.java index 7c8158a8..d4810830 100644 --- a/src/main/java/net/fabricmc/loom/util/ipc/IPCServer.java +++ b/src/main/java/net/fabricmc/loom/util/ipc/IPCServer.java @@ -46,6 +46,8 @@ public class IPCServer implements AutoCloseable { private final CountDownLatch startupLock = new CountDownLatch(1); + private boolean receivedMessage = false; + public IPCServer(Path path, Consumer consumer) { this.path = path; this.consumer = consumer; @@ -71,6 +73,7 @@ public class IPCServer implements AutoCloseable { Scanner scanner = new Scanner(clientChannel, StandardCharsets.UTF_8)) { while (!Thread.currentThread().isInterrupted()) { if (scanner.hasNextLine()) { + receivedMessage = true; this.consumer.accept(scanner.nextLine()); } } @@ -85,4 +88,12 @@ public class IPCServer implements AutoCloseable { loggerReceiverService.shutdownNow(); loggerReceiverService.awaitTermination(10, TimeUnit.SECONDS); } + + public boolean hasReceivedMessage() { + return receivedMessage; + } + + public Path getPath() { + return path; + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java b/src/main/java/net/fabricmc/loom/util/service/SharedService.java similarity index 75% rename from src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java rename to src/main/java/net/fabricmc/loom/util/service/SharedService.java index d8e4a779..53cb2c7c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/MinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/util/service/SharedService.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2018-2021 FabricMC + * 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 @@ -22,20 +22,13 @@ * SOFTWARE. */ -package net.fabricmc.loom.configuration.providers; +package net.fabricmc.loom.util.service; -import java.io.File; +import java.io.Closeable; +import java.io.IOException; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; - -public interface MinecraftProvider { - File workingDir(); - - File dir(String path); - - File file(String path); - - String minecraftVersion(); - - MinecraftVersionMeta getVersionInfo(); +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/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt new file mode 100644 index 00000000..f847fc19 --- /dev/null +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt @@ -0,0 +1,124 @@ +/* + * 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.kotlin.remapping + +import kotlinx.metadata.jvm.KotlinClassHeader +import kotlinx.metadata.jvm.KotlinClassMetadata +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.commons.Remapper +import org.objectweb.asm.tree.AnnotationNode + +class KotlinClassMetadataRemappingAnnotationVisitor(private val remapper: Remapper, val next: AnnotationVisitor) : + AnnotationNode(Opcodes.ASM9, KotlinMetadataRemappingClassVisitor.ANNOTATION_DESCRIPTOR) { + + private var _name: String? = null + + override fun visit(name: String?, value: Any?) { + super.visit(name, value) + this._name = name + } + + override fun visitEnd() { + super.visitEnd() + when (val metadata = readMetadata()) { + is KotlinClassMetadata.Class -> { + val klass = metadata.toKmClass() + val writer = KotlinClassMetadata.Class.Writer() + klass.accept(RemappingKmVisitors(remapper).RemappingKmClassVisitor(writer)) + writeClassHeader(writer.write().header) + } + is KotlinClassMetadata.SyntheticClass -> { + val klambda = metadata.toKmLambda() + + if (klambda != null) { + val writer = KotlinClassMetadata.SyntheticClass.Writer() + klambda.accept(RemappingKmVisitors(remapper).RemappingKmLambdaVisitor(writer)) + writeClassHeader(writer.write().header) + } else { + accept(next) + } + } + // Can only be turned into KmPackage which is useless data + is KotlinClassMetadata.FileFacade, is KotlinClassMetadata.MultiFileClassPart, + // Can't be turned into data + is KotlinClassMetadata.MultiFileClassFacade, is KotlinClassMetadata.Unknown, null -> { + // do nothing + accept(next) + } + } + } + + @Suppress("UNCHECKED_CAST") + private fun readMetadata(): KotlinClassMetadata? { + var kind: Int? = null + var metadataVersion: IntArray? = null + var data1: Array? = null + var data2: Array? = null + var extraString: String? = null + var packageName: String? = null + var extraInt: Int? = null + + if (values == null) { + return null + } + + values.chunked(2).forEach { (name, value) -> + when (name) { + "k" -> kind = value as Int + "mv" -> metadataVersion = (value as List).toIntArray() + "d1" -> data1 = (value as List).toTypedArray() + "d2" -> data2 = (value as List).toTypedArray() + "xs" -> extraString = value as String + "pn" -> packageName = value as String + "xi" -> extraInt = value as Int + } + } + + val header = KotlinClassHeader(kind, metadataVersion, data1, data2, extraString, packageName, extraInt) + return KotlinClassMetadata.read(header) + } + + private fun writeClassHeader(header: KotlinClassHeader) { + val newNode = AnnotationNode(api, desc) + newNode.values = this.values.toMutableList() + + newNode.run { + for (i in values.indices step 2) { + when (values[i]) { + "k" -> values[i + 1] = header.kind + "mv" -> values[i + 1] = header.metadataVersion.toList() + "d1" -> values[i + 1] = header.data1.toList() + "d2" -> values[i + 1] = header.data2.toList() + "xs" -> values[i + 1] = header.extraString + "pn" -> values[i + 1] = header.packageName + "xi" -> values[i + 1] = header.extraInt + } + } + } + + newNode.accept(next) + } +} diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt new file mode 100644 index 00000000..f6716a89 --- /dev/null +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataRemappingClassVisitor.kt @@ -0,0 +1,47 @@ +/* + * 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.kotlin.remapping + +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.objectweb.asm.commons.Remapper + +class KotlinMetadataRemappingClassVisitor(private val remapper: Remapper, next: ClassVisitor?) : ClassVisitor(Opcodes.ASM9, next) { + companion object { + val ANNOTATION_DESCRIPTOR: String = Type.getDescriptor(Metadata::class.java) + } + + override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { + var result: AnnotationVisitor? = super.visitAnnotation(descriptor, visible) + + if (descriptor == ANNOTATION_DESCRIPTOR && result != null) { + result = KotlinClassMetadataRemappingAnnotationVisitor(remapper, result) + } + + return result + } +} diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt new file mode 100644 index 00000000..1ba80919 --- /dev/null +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinMetadataTinyRemapperExtension.kt @@ -0,0 +1,39 @@ +/* + * 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.kotlin.remapping + +import net.fabricmc.tinyremapper.TinyRemapper +import net.fabricmc.tinyremapper.api.TrClass +import org.objectweb.asm.ClassVisitor + +object KotlinMetadataTinyRemapperExtension : TinyRemapper.ApplyVisitorProvider, TinyRemapper.Extension { + override fun insertApplyVisitor(cls: TrClass, next: ClassVisitor): ClassVisitor { + return KotlinMetadataRemappingClassVisitor(cls.environment.remapper, next) + } + + override fun attach(builder: TinyRemapper.Builder) { + builder.extraPreApplyVisitor(this) + } +} diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/RemappingKmVisitors.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/RemappingKmVisitors.kt new file mode 100644 index 00000000..3acbd24a --- /dev/null +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/RemappingKmVisitors.kt @@ -0,0 +1,359 @@ +/* + * 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.kotlin.remapping + +import kotlinx.metadata.ClassName +import kotlinx.metadata.Flags +import kotlinx.metadata.KmAnnotation +import kotlinx.metadata.KmClassExtensionVisitor +import kotlinx.metadata.KmClassVisitor +import kotlinx.metadata.KmConstructorExtensionVisitor +import kotlinx.metadata.KmConstructorVisitor +import kotlinx.metadata.KmContractVisitor +import kotlinx.metadata.KmEffectExpressionVisitor +import kotlinx.metadata.KmEffectInvocationKind +import kotlinx.metadata.KmEffectType +import kotlinx.metadata.KmEffectVisitor +import kotlinx.metadata.KmExtensionType +import kotlinx.metadata.KmFunctionExtensionVisitor +import kotlinx.metadata.KmFunctionVisitor +import kotlinx.metadata.KmLambdaVisitor +import kotlinx.metadata.KmPropertyExtensionVisitor +import kotlinx.metadata.KmPropertyVisitor +import kotlinx.metadata.KmTypeAliasVisitor +import kotlinx.metadata.KmTypeExtensionVisitor +import kotlinx.metadata.KmTypeParameterExtensionVisitor +import kotlinx.metadata.KmTypeParameterVisitor +import kotlinx.metadata.KmTypeVisitor +import kotlinx.metadata.KmValueParameterVisitor +import kotlinx.metadata.KmVariance +import kotlinx.metadata.jvm.JvmClassExtensionVisitor +import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor +import kotlinx.metadata.jvm.JvmFieldSignature +import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor +import kotlinx.metadata.jvm.JvmMethodSignature +import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor +import kotlinx.metadata.jvm.JvmTypeExtensionVisitor +import kotlinx.metadata.jvm.JvmTypeParameterExtensionVisitor +import org.objectweb.asm.commons.Remapper + +class RemappingKmVisitors(private val remapper: Remapper) { + private fun remapJvmMethodSignature(signature: JvmMethodSignature?): JvmMethodSignature? { + if (signature != null) { + return JvmMethodSignature(signature.name, remapper.mapMethodDesc(signature.desc)) + } + + return null + } + + private fun remapJvmFieldSignature(signature: JvmFieldSignature?): JvmFieldSignature? { + if (signature != null) { + return JvmFieldSignature(signature.name, remapper.mapDesc(signature.desc)) + } + + return null + } + + inner class RemappingKmClassVisitor(delegate: KmClassVisitor?) : KmClassVisitor(delegate) { + override fun visit(flags: Flags, name: ClassName) { + super.visit(flags, remapper.map(name)) + } + + override fun visitNestedClass(name: String) { + super.visitNestedClass(remapper.map(name)) + } + + override fun visitSealedSubclass(name: ClassName) { + super.visitSealedSubclass(remapper.map(name)) + } + + override fun visitSupertype(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitSupertype(flags)) + } + + override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor { + return RemappingKmFunctionVisitor(super.visitFunction(flags, name)) + } + + override fun visitInlineClassUnderlyingType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitInlineClassUnderlyingType(flags)) + } + + override fun visitProperty(flags: Flags, name: String, getterFlags: Flags, setterFlags: Flags): KmPropertyVisitor { + return RemappingKmPropertyVisitor(super.visitProperty(flags, name, getterFlags, setterFlags)) + } + + override fun visitTypeAlias(flags: Flags, name: String): KmTypeAliasVisitor { + return RemappingKmTypeAliasVisitor(super.visitTypeAlias(flags, name)) + } + + override fun visitConstructor(flags: Flags): KmConstructorVisitor { + return RemappingKmConstructorVisitor(super.visitConstructor(flags)) + } + + override fun visitExtensions(type: KmExtensionType): KmClassExtensionVisitor { + return RemappingJvmClassExtensionVisitor(super.visitExtensions(type) as JvmClassExtensionVisitor?) + } + + override fun visitTypeParameter( + flags: Flags, + name: String, + id: Int, + variance: KmVariance + ): KmTypeParameterVisitor { + return RemappingKmTypeParameterVisitor(super.visitTypeParameter(flags, name, id, variance)) + } + } + + inner class RemappingKmLambdaVisitor(delegate: KmLambdaVisitor?) : KmLambdaVisitor(delegate) { + override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor { + return RemappingKmFunctionVisitor(super.visitFunction(flags, name)) + } + } + + inner class RemappingKmTypeVisitor(delegate: KmTypeVisitor?) : KmTypeVisitor(delegate) { + override fun visitClass(name: ClassName) { + super.visitClass(remapper.map(name)) + } + + override fun visitTypeAlias(name: ClassName) { + super.visitTypeAlias(remapper.map(name)) + } + + override fun visitAbbreviatedType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitAbbreviatedType(flags)) + } + + override fun visitArgument(flags: Flags, variance: KmVariance): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitArgument(flags, variance)) + } + + override fun visitOuterType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitOuterType(flags)) + } + + override fun visitFlexibleTypeUpperBound(flags: Flags, typeFlexibilityId: String?): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitFlexibleTypeUpperBound(flags, typeFlexibilityId)) + } + + override fun visitExtensions(type: KmExtensionType): KmTypeExtensionVisitor { + return RemappingJvmTypeExtensionVisitor(super.visitExtensions(type) as JvmTypeExtensionVisitor?) + } + } + + inner class RemappingJvmTypeExtensionVisitor(delegate: JvmTypeExtensionVisitor?) : JvmTypeExtensionVisitor(delegate) { + override fun visitAnnotation(annotation: KmAnnotation) { + super.visitAnnotation(KmAnnotation(remapper.map(annotation.className), annotation.arguments)) + } + } + + inner class RemappingKmFunctionVisitor(delegate: KmFunctionVisitor?) : KmFunctionVisitor(delegate) { + override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitReceiverParameterType(flags)) + } + + override fun visitReturnType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitReturnType(flags)) + } + + override fun visitValueParameter(flags: Flags, name: String): KmValueParameterVisitor { + return RemappingKmValueParameterVisitor(super.visitValueParameter(flags, name)) + } + + override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor { + return RemappingJvmFunctionExtensionVisitor(super.visitExtensions(type) as JvmFunctionExtensionVisitor) + } + + override fun visitContract(): KmContractVisitor { + return RemappingKmContractVisitor(super.visitContract()) + } + } + + inner class RemappingKmContractVisitor(delegate: KmContractVisitor?) : KmContractVisitor(delegate) { + override fun visitEffect(type: KmEffectType, invocationKind: KmEffectInvocationKind?): KmEffectVisitor { + return RemappingKmEffectVisitor(super.visitEffect(type, invocationKind)) + } + } + + inner class RemappingKmEffectVisitor(delegate: KmEffectVisitor?) : KmEffectVisitor(delegate) { + override fun visitConclusionOfConditionalEffect(): KmEffectExpressionVisitor { + return RemappingKmEffectExpressionVisitor(super.visitConclusionOfConditionalEffect()) + } + + override fun visitConstructorArgument(): KmEffectExpressionVisitor { + return RemappingKmEffectExpressionVisitor(super.visitConclusionOfConditionalEffect()) + } + } + + inner class RemappingKmEffectExpressionVisitor(delegate: KmEffectExpressionVisitor?) : KmEffectExpressionVisitor(delegate) { + override fun visitAndArgument(): KmEffectExpressionVisitor { + return RemappingKmEffectExpressionVisitor(super.visitAndArgument()) + } + + override fun visitIsInstanceType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitIsInstanceType(flags)) + } + + override fun visitOrArgument(): KmEffectExpressionVisitor { + return RemappingKmEffectExpressionVisitor(super.visitOrArgument()) + } + } + + inner class RemappingKmPropertyVisitor(delegate: KmPropertyVisitor?) : KmPropertyVisitor(delegate) { + override fun visitReceiverParameterType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitReceiverParameterType(flags)) + } + + override fun visitReturnType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitReturnType(flags)) + } + + override fun visitSetterParameter(flags: Flags, name: String): KmValueParameterVisitor { + return RemappingKmValueParameterVisitor(super.visitSetterParameter(flags, name)) + } + + override fun visitExtensions(type: KmExtensionType): KmPropertyExtensionVisitor { + return RemappingJvmPropertyExtensionVisitor(super.visitExtensions(type) as JvmPropertyExtensionVisitor?) + } + + override fun visitTypeParameter( + flags: Flags, + name: String, + id: Int, + variance: KmVariance + ): KmTypeParameterVisitor { + return RemappingKmTypeParameterVisitor(super.visitTypeParameter(flags, name, id, variance)) + } + } + + inner class RemappingJvmPropertyExtensionVisitor(delegate: JvmPropertyExtensionVisitor?) : JvmPropertyExtensionVisitor(delegate) { + override fun visit( + jvmFlags: Flags, + fieldSignature: JvmFieldSignature?, + getterSignature: JvmMethodSignature?, + setterSignature: JvmMethodSignature? + ) { + super.visit(jvmFlags, remapJvmFieldSignature(fieldSignature), remapJvmMethodSignature(getterSignature), remapJvmMethodSignature(setterSignature)) + } + + override fun visitSyntheticMethodForAnnotations(signature: JvmMethodSignature?) { + super.visitSyntheticMethodForAnnotations(remapJvmMethodSignature(signature)) + } + + override fun visitSyntheticMethodForDelegate(signature: JvmMethodSignature?) { + super.visitSyntheticMethodForDelegate(remapJvmMethodSignature(signature)) + } + } + + inner class RemappingKmTypeParameterVisitor(delegate: KmTypeParameterVisitor?) : KmTypeParameterVisitor(delegate) { + override fun visitExtensions(type: KmExtensionType): KmTypeParameterExtensionVisitor { + return RemappingJvmTypeParameterExtensionVisitor(super.visitExtensions(type) as JvmTypeParameterExtensionVisitor?) + } + + override fun visitUpperBound(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitUpperBound(flags)) + } + } + + inner class RemappingJvmTypeParameterExtensionVisitor(delegate: JvmTypeParameterExtensionVisitor?) : JvmTypeParameterExtensionVisitor(delegate) { + override fun visitAnnotation(annotation: KmAnnotation) { + super.visitAnnotation(KmAnnotation(remapper.map(annotation.className), annotation.arguments)) + } + } + + inner class RemappingKmValueParameterVisitor(delegate: KmValueParameterVisitor?) : KmValueParameterVisitor(delegate) { + override fun visitType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitType(flags)) + } + + override fun visitVarargElementType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitVarargElementType(flags)) + } + } + + inner class RemappingKmTypeAliasVisitor(delegate: KmTypeAliasVisitor?) : KmTypeAliasVisitor(delegate) { + override fun visitExpandedType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitExpandedType(flags)) + } + + override fun visitTypeParameter( + flags: Flags, + name: String, + id: Int, + variance: KmVariance + ): KmTypeParameterVisitor { + return RemappingKmTypeParameterVisitor(super.visitTypeParameter(flags, name, id, variance)) + } + + override fun visitUnderlyingType(flags: Flags): KmTypeVisitor { + return RemappingKmTypeVisitor(super.visitUnderlyingType(flags)) + } + + override fun visitAnnotation(annotation: KmAnnotation) { + super.visitAnnotation(KmAnnotation(remapper.map(annotation.className), annotation.arguments)) + } + } + + inner class RemappingJvmFunctionExtensionVisitor(delegate: JvmFunctionExtensionVisitor?) : JvmFunctionExtensionVisitor(delegate) { + override fun visit(signature: JvmMethodSignature?) { + super.visit(remapJvmMethodSignature(signature)) + } + + override fun visitLambdaClassOriginName(internalName: String) { + super.visitLambdaClassOriginName(remapper.map(internalName)) + } + } + + inner class RemappingJvmClassExtensionVisitor(delegate: JvmClassExtensionVisitor?) : JvmClassExtensionVisitor(delegate) { + override fun visitAnonymousObjectOriginName(internalName: String) { + super.visitAnonymousObjectOriginName(remapper.map(internalName)) + } + + override fun visitLocalDelegatedProperty( + flags: Flags, + name: String, + getterFlags: Flags, + setterFlags: Flags + ): KmPropertyVisitor { + return RemappingKmPropertyVisitor(super.visitLocalDelegatedProperty(flags, name, getterFlags, setterFlags)) + } + } + + inner class RemappingKmConstructorVisitor(delegate: KmConstructorVisitor?) : KmConstructorVisitor(delegate) { + override fun visitExtensions(type: KmExtensionType): KmConstructorExtensionVisitor { + return RemappingJvmConstructorExtensionVisitor(super.visitExtensions(type) as JvmConstructorExtensionVisitor?) + } + + override fun visitValueParameter(flags: Flags, name: String): KmValueParameterVisitor { + return RemappingKmValueParameterVisitor(super.visitValueParameter(flags, name)) + } + } + + inner class RemappingJvmConstructorExtensionVisitor(delegate: JvmConstructorExtensionVisitor?) : JvmConstructorExtensionVisitor(delegate) { + override fun visit(signature: JvmMethodSignature?) { + super.visit(remapJvmMethodSignature(signature)) + } + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy b/src/test/groovy/net/fabricmc/loom/test/LoomTestConstants.groovy index 01cc67ba..3b04a3e4 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-20211228231407+0000" + public final static String PRE_RELEASE_GRADLE = "7.5-20220116010338+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/benchmark/SimpleBenchmark.groovy b/src/test/groovy/net/fabricmc/loom/test/benchmark/SimpleBenchmark.groovy new file mode 100644 index 00000000..6fd7976c --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/benchmark/SimpleBenchmark.groovy @@ -0,0 +1,75 @@ +/* + * 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 + +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +/** + * 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 SimpleBenchmark implements GradleProjectTestTrait { + def run(File dir) { + // Forces loom to refresh files + System.setProperty("loom.refresh", "true") + + def gradle = gradleProject( + project: "minimalBase", + version: LoomTestConstants.PRE_RELEASE_GRADLE, + projectDir: new File(dir, "project"), + gradleHomeDir: new File(dir, "gradlehome") + ) + + gradle.buildGradle << ''' + dependencies { + minecraft "com.mojang:minecraft:1.18.1" + mappings "net.fabricmc:yarn:1.18.1+build.17:v2" + modImplementation "net.fabricmc.fabric-api:fabric-api:0.45.1+1.18" + } + ''' + + def timeStart = new Date() + + def result = gradle.run(tasks: ["clean", "build"]) + + def timeStop = new Date() + TimeDuration duration = TimeCategory.minus(timeStop, timeStart) + println(duration) + + assert result.task(":build").outcome == SUCCESS + + System.exit(0) + } + + static void main(String[] args) { + getInstance().run(new File(args[0])) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/KotlinTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/KotlinTest.groovy index 91cf813d..afe06b03 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/KotlinTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/KotlinTest.groovy @@ -25,6 +25,7 @@ package net.fabricmc.loom.test.integration import net.fabricmc.loom.test.util.GradleProjectTestTrait +import net.fabricmc.loom.test.util.ServerRunner import spock.lang.Specification import spock.lang.Unroll @@ -36,12 +37,17 @@ class KotlinTest extends Specification implements GradleProjectTestTrait { def "kotlin build (gradle #version)"() { setup: def gradle = gradleProject(project: "kotlin", version: version) + def server = ServerRunner.create(gradle.projectDir, "1.16.5") + .withMod(gradle.getOutputFile("fabric-example-mod-0.0.1.jar")) + .downloadMod(ServerRunner.FABRIC_LANG_KOTLIN, "fabric-language-kotlin-1.7.1+kotlin.1.6.10.jar") when: def result = gradle.run(task: "build") + def serverResult = server.run() then: result.task(":build").outcome == SUCCESS + serverResult.successful() where: version << STANDARD_TEST_VERSIONS diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/MCJarConfigTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/MCJarConfigTest.groovy new file mode 100644 index 00000000..5df77f3e --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/MCJarConfigTest.groovy @@ -0,0 +1,88 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.integration + +import net.fabricmc.loom.test.util.GradleProjectTestTrait +import spock.lang.Specification +import spock.lang.Unroll + +import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class MCJarConfigTest extends Specification implements GradleProjectTestTrait { + @Unroll + def "server only (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + + gradle.buildGradle << ''' + loom { + serverOnlyMinecraftJar() + } + + dependencies { + minecraft "com.mojang:minecraft:1.18.1" + mappings "net.fabricmc:yarn:1.18.1+build.18:v2" + modImplementation "net.fabricmc:fabric-loader:0.12.12" + } + ''' + + when: + def result = gradle.run(task: "build") + + then: + result.task(":build").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } + + @Unroll + def "split (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + + gradle.buildGradle << ''' + loom { + splitMinecraftJar() + } + + dependencies { + minecraft "com.mojang:minecraft:1.18.1" + mappings "net.fabricmc:yarn:1.18.1+build.18:v2" + modImplementation "net.fabricmc:fabric-loader:0.12.12" + } + ''' + + when: + def result = gradle.run(task: "build") + + then: + result.task(":build").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/MinecraftJarSplitterTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/MinecraftJarSplitterTest.groovy new file mode 100644 index 00000000..0b6d9793 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/MinecraftJarSplitterTest.groovy @@ -0,0 +1,69 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit + +import net.fabricmc.loom.configuration.providers.BundleMetadata +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJarSplitter +import spock.lang.Specification + +class MinecraftJarSplitterTest extends Specification { + public static final String CLIENT_JAR_URL = "https://launcher.mojang.com/v1/objects/7e46fb47609401970e2818989fa584fd467cd036/client.jar" + public static final String SERVER_BUNDLE_JAR_URL = "https://launcher.mojang.com/v1/objects/125e5adf40c659fd3bce3e66e67a16bb49ecc1b9/server.jar" + + public static final File mcJarDir = File.createTempDir() + + def "split jars"() { + given: + def clientJar = downloadJarIfNotExists(CLIENT_JAR_URL, "client.jar") + def serverBundleJar = downloadJarIfNotExists(SERVER_BUNDLE_JAR_URL, "server_bundle.jar") + def serverJar = new File(mcJarDir, "server.jar") + + def clientOnlyJar = new File(mcJarDir, "client_only.jar") + def commonJar = new File(mcJarDir, "common.jar") + when: + def serverBundleMetadata = BundleMetadata.fromJar(serverBundleJar.toPath()) + serverBundleMetadata.versions().find().unpackEntry(serverBundleJar.toPath(), serverJar.toPath()) + + clientOnlyJar.delete() + commonJar.delete() + + new MinecraftJarSplitter(clientJar.toPath(), serverJar.toPath()).withCloseable { + it.split(clientOnlyJar.toPath(), commonJar.toPath()) + } + then: + serverBundleMetadata.versions().size() == 1 + } + + File downloadJarIfNotExists(String url, String name) { + File dst = new File(mcJarDir, name) + + if (!dst.exists()) { + dst.parentFile.mkdirs() + dst << new URL(url).newInputStream() + } + + return dst + } +} 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 f751f3ab..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.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 fa08793d..45b5cb50 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) @@ -194,6 +199,10 @@ trait GradleProjectTestTrait { return new File(getProjectDir(), "build.gradle") } + File getGradleProperties() { + return new File(getProjectDir(), "gradle.properties") + } + String getOutputZipEntry(String filename, String entryName) { def file = getOutputFile(filename) def bytes = ZipUtils.unpackNullable(file.toPath(), entryName) @@ -211,7 +220,7 @@ trait GradleProjectTestTrait { } File getGeneratedSources(String mappings) { - return new File(getGradleHomeDir(), "caches/fabric-loom/${mappings}/minecraft-mapped-sources.jar") + return new File(getGradleHomeDir(), "caches/fabric-loom/${mappings}/minecraft-merged-named-sources.jar") } } } \ No newline at end of file diff --git a/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy b/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy index 82b519d9..0a95fd7d 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy @@ -34,6 +34,7 @@ class ServerRunner { "1.16.5": "https://github.com/FabricMC/fabric/releases/download/0.37.1%2B1.16/fabric-api-0.37.1+1.16.jar", "1.17.1": "https://github.com/FabricMC/fabric/releases/download/0.37.1%2B1.17/fabric-api-0.37.1+1.17.jar" ] + static final String FABRIC_LANG_KOTLIN = "https://maven.fabricmc.net/net/fabricmc/fabric-language-kotlin/1.7.1%2Bkotlin.1.6.10/fabric-language-kotlin-1.7.1%2Bkotlin.1.6.10.jar" final File serverDir final String minecraftVersion diff --git a/src/test/kotlin/net/fabricmc/loom/test/kotlin/KotlinClassMetadataRemappingAnnotationVisitorTest.kt b/src/test/kotlin/net/fabricmc/loom/test/kotlin/KotlinClassMetadataRemappingAnnotationVisitorTest.kt new file mode 100644 index 00000000..369beaaa --- /dev/null +++ b/src/test/kotlin/net/fabricmc/loom/test/kotlin/KotlinClassMetadataRemappingAnnotationVisitorTest.kt @@ -0,0 +1,82 @@ +/* + * 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.kotlin + +import java.io.File +import java.io.PrintWriter +import java.io.StringWriter +import java.nio.file.Paths +import net.fabricmc.loom.kotlin.remapping.KotlinMetadataRemappingClassVisitor +import net.fabricmc.loom.util.TinyRemapperHelper +import net.fabricmc.mappingio.MappingReader +import net.fabricmc.mappingio.tree.MemoryMappingTree +import net.fabricmc.tinyremapper.IMappingProvider +import net.fabricmc.tinyremapper.TinyRemapper +import org.junit.jupiter.api.Test +import org.objectweb.asm.ClassReader +import org.objectweb.asm.Opcodes +import org.objectweb.asm.util.Textifier +import org.objectweb.asm.util.TraceClassVisitor + +// See: https://github.com/JetBrains/kotlin/blob/master/libraries/kotlinx-metadata/jvm/test/kotlinx/metadata/test/MetadataSmokeTest.kt#L67 +class KotlinClassMetadataRemappingAnnotationVisitorTest { + /* + val test = KmClass() + klass.accept(RemappingKmClassVisitor(remapper, test)) + println(GsonBuilder().setPrettyPrinting().create().toJson(test)) + */ + + @Test + fun simpleTest() { + val inputPosInChunk = getClassBytes("PosInChunk") + + val classReader = ClassReader(inputPosInChunk) + + val tinyRemapper = TinyRemapper.newRemapper() + .withMappings(readMappings("PosInChunk")) + .build() + + val stringWriter = StringWriter() + val traceClassVisitor = TraceClassVisitor(null, TextifierImpl(), PrintWriter(stringWriter)) + + classReader.accept(KotlinMetadataRemappingClassVisitor(tinyRemapper.environment.remapper, traceClassVisitor), 0) + + val d2Regex = Regex("(d2=)(.*)") + val asm = stringWriter.toString() + println(d2Regex.find(asm)?.value) + } + + private fun getClassBytes(name: String): ByteArray { + return File("src/test/resources/classes/$name.class").readBytes() + } + + private fun readMappings(name: String): IMappingProvider { + val mappingTree = MemoryMappingTree() + MappingReader.read(Paths.get("src/test/resources/mappings/$name.mappings"), mappingTree) + return TinyRemapperHelper.create(mappingTree, "named", "intermediary", false) + } + + private class TextifierImpl : Textifier(Opcodes.ASM9) +} diff --git a/src/test/resources/classes/PosInChunk.class b/src/test/resources/classes/PosInChunk.class new file mode 100644 index 00000000..8d00faef Binary files /dev/null and b/src/test/resources/classes/PosInChunk.class differ diff --git a/src/test/resources/mappings/PosInChunk.mappings b/src/test/resources/mappings/PosInChunk.mappings new file mode 100644 index 00000000..c6f1d3c4 --- /dev/null +++ b/src/test/resources/mappings/PosInChunk.mappings @@ -0,0 +1,220 @@ +tiny 2 0 intermediary named +c net/minecraft/class_2338 net/minecraft/util/math/BlockPos + c Represents the position of a block in a three-dimensional volume.\n\n

The position is integer-valued.\n\n

A block position may be mutable; hence, when using block positions\nobtained from other places as map keys, etc., you should call {@link\n#toImmutable()} to obtain an immutable block position. + f J field_10976 BITS_X + f Lcom/mojang/serialization/Codec; field_25064 CODEC + f Lorg/apache/logging/log4j/Logger; field_18789 LOGGER + f Lnet/minecraft/class_2338; field_10980 ORIGIN + c The block position which x, y, and z values are all zero. + f I field_10983 BIT_SHIFT_Z + f I field_10975 SIZE_BITS_Y + f J field_10974 BITS_Y + f I field_10978 SIZE_BITS_X + f I field_10981 BIT_SHIFT_X + f J field_10973 BITS_Z + f I field_10977 SIZE_BITS_Z + m ([I)Lnet/minecraft/class_2338; method_29095 method_29095 + p 0 values + m (Ljava/util/Random;IIIIIII)Ljava/lang/Iterable; method_27156 iterateRandomly + c Iterates through {@code count} random block positions in the given area.\n\n

The iterator yields positions in no specific order. The same position\nmay be returned multiple times by the iterator. + p 6 maxY + c the maximum y value for returned positions + p 7 maxZ + c the maximum z value for returned positions + p 4 minZ + c the minimum z value for returned positions + p 5 maxX + c the maximum x value for returned positions + p 2 minX + c the minimum x value for returned positions + p 3 minY + c the minimum y value for returned positions + p 0 random + c the {@link Random} object used to compute new positions + p 1 count + c the number of positions to iterate + m (JIII)J method_10096 add + p 0 value + p 3 y + p 2 x + p 4 z + m ()Lnet/minecraft/class_2338$class_2339; method_25503 mutableCopy + c Returns a mutable copy of this block position.\n\n

If this block position is a mutable one, mutation to this block\nposition won't affect the returned position. + m (I)Lnet/minecraft/class_2338; method_10088 west + p 1 distance + m (I)Lnet/minecraft/class_2338; method_10076 north + p 1 distance + m ()J method_10063 asLong + m ()Lnet/minecraft/class_2338; method_10084 up + m (Lnet/minecraft/class_2382;)V + p 1 pos + m (Lnet/minecraft/class_2338;III)Ljava/util/stream/Stream; method_25998 streamOutwards + p 0 center + p 3 maxZ + p 1 maxX + p 2 maxY + m (Lnet/minecraft/class_2374;)V + p 1 pos + m (Lnet/minecraft/class_2382;)Lnet/minecraft/class_2338; method_10081 add + m (Lnet/minecraft/class_238;)Ljava/util/stream/Stream; method_29715 stream + p 0 box + m ()Lnet/minecraft/class_2338; method_10072 south + m (III)Lnet/minecraft/class_2338; method_10069 add + m (I)Lnet/minecraft/class_2338; method_35830 multiply + m (Ljava/util/Random;ILnet/minecraft/class_2338;I)Ljava/lang/Iterable; method_34848 iterateRandomly + c Iterates through {@code count} random block positions in a given range around the given position.\n\n

The iterator yields positions in no specific order. The same position\nmay be returned multiple times by the iterator. + p 0 random + c the {@link Random} object used to compute new positions + p 1 count + c the number of positions to iterate + p 2 around + c the {@link BlockPos} to iterate around + p 3 range + c the maximum distance from the given pos in any axis + m (I)Lnet/minecraft/class_2338; method_10077 south + p 1 distance + m (I)Lnet/minecraft/class_2338; method_10089 east + p 1 distance + m (J)J method_10091 removeChunkSectionLocalY + p 0 y + m (J)I method_10071 unpackLongY + p 0 packedPos + m (Lnet/minecraft/class_2338;ILnet/minecraft/class_2350;Lnet/minecraft/class_2350;)Ljava/lang/Iterable; method_30512 iterateInSquare + c Iterates block positions around the {@code center} in a square of\n({@code 2 * radius + 1}) by ({@code 2 * radius + 1}). The blocks\nare iterated in a (square) spiral around the center.\n\n

The first block returned is the center, then the iterator moves\na block towards the first direction, followed by moving along\nthe second direction.\n\n@throws IllegalStateException when the 2 directions lie on the same axis + p 2 firstDirection + c the direction the iterator moves first + p 3 secondDirection + c the direction the iterator moves after the first + p 0 center + c the center of iteration + p 1 radius + c the maximum chebychev distance + m (J)I method_10083 unpackLongZ + p 0 packedPos + m (J)Lnet/minecraft/class_2338; method_10092 fromLong + p 0 packedPos + m (I)Lnet/minecraft/class_2338; method_33096 withY + p 1 y + m (Ljava/util/stream/IntStream;)Lcom/mojang/serialization/DataResult; method_29094 method_29094 + p 0 stream + m (Lnet/minecraft/class_2338;Lnet/minecraft/class_2338;)Ljava/util/stream/Stream; method_20437 stream + p 1 end + p 0 start + m (Lnet/minecraft/class_2350;)Lnet/minecraft/class_2338; method_10093 offset + m (I)Lnet/minecraft/class_2338; method_10086 up + p 1 distance + m (III)J method_10064 asLong + p 0 x + p 1 y + p 2 z + m ()Lnet/minecraft/class_2338; method_10074 down + m (IIIIII)Ljava/lang/Iterable; method_10094 iterate + p 1 startY + p 0 startX + p 3 endX + p 2 startZ + p 5 endZ + p 4 endY + m ()Lnet/minecraft/class_2338; method_10062 toImmutable + c Returns an immutable block position with the same x, y, and z as this\nposition.\n\n

This method should be called when a block position is used as map\nkeys as to prevent side effects of mutations of mutable block positions. + m ()Lnet/minecraft/class_2338; method_10078 east + m (Lnet/minecraft/class_3341;)Ljava/util/stream/Stream; method_23627 stream + p 0 box + m (Lnet/minecraft/class_2350;I)Lnet/minecraft/class_2338; method_10079 offset + m (Lnet/minecraft/class_2338;III)Ljava/lang/Iterable; method_25996 iterateOutwards + c Iterates block positions around the {@code center}. The iteration order\nis mainly based on the manhattan distance of the position from the\ncenter.\n\n

For the same manhattan distance, the positions are iterated by y\noffset, from negative to positive. For the same y offset, the positions\nare iterated by x offset, from negative to positive. For the two\npositions with the same x and y offsets and the same manhattan distance,\nthe one with a positive z offset is visited first before the one with a\nnegative z offset. + p 2 rangeY + c the maximum y difference from the center + p 3 rangeZ + c the maximum z difference from the center + p 0 center + c the center of iteration + p 1 rangeX + c the maximum x difference from the center + m (Lnet/minecraft/class_2338;)Ljava/util/stream/IntStream; method_29093 method_29093 + p 0 pos + m (Lnet/minecraft/class_2382;)Lnet/minecraft/class_2338; method_10059 subtract + m (Lnet/minecraft/class_2382;)Lnet/minecraft/class_2338; method_10075 crossProduct + p 1 pos + m (IIIIII)Ljava/util/stream/Stream; method_17962 stream + p 1 startY + p 0 startX + p 3 endX + p 2 startZ + p 5 endZ + p 4 endY + m (Lnet/minecraft/class_2350$class_2351;I)Lnet/minecraft/class_2338; method_30513 offset + m (Lnet/minecraft/class_2338;IILjava/util/function/Predicate;)Ljava/util/Optional; method_25997 findClosest + p 3 condition + p 1 horizontalRange + p 2 verticalRange + p 0 pos + m (I)Lnet/minecraft/class_2338; method_10087 down + m (DDD)Lnet/minecraft/class_2338; method_10080 add + m (Lnet/minecraft/class_2338;Lnet/minecraft/class_2338;)Ljava/lang/Iterable; method_10097 iterate + p 1 end + p 0 start + m ()Lnet/minecraft/class_2338; method_10095 north + m ()Lnet/minecraft/class_2338; method_10067 west + m (J)I method_10061 unpackLongX + p 0 packedPos + m (Lnet/minecraft/class_243;)V + p 1 pos + m (JLnet/minecraft/class_2350;)J method_10060 offset + p 2 direction + p 0 value + m (Lnet/minecraft/class_2470;)Lnet/minecraft/class_2338; method_10070 rotate + p 1 rotationrotate +c net/minecraft/class_1923 net/minecraft/util/math/ChunkPos + f I field_9180 z + f I field_9181 x + f Lnet/minecraft/class_1923; field_35107 ORIGIN + f J field_17348 MARKER + m ()Lnet/minecraft/class_2338; method_8323 getStartPos + m (Lnet/minecraft/class_1923;)I method_24022 getChebyshevDistance + p 1 pos + m ()J method_8324 toLong + m (II)J method_8331 toLong + p 1 chunkZ + p 0 chunkX + m ()I method_17887 getRegionRelativeX + m ()I method_17885 getRegionX + m (J)V + p 1 pos + m ()I method_33940 getCenterX + m (J)I method_8325 getPackedX + p 0 pos + m (Lnet/minecraft/class_2338;)V + p 1 pos + m ()I method_33942 getCenterZ + m ()I method_8326 getStartX + m ()I method_8328 getStartZ + m (III)Lnet/minecraft/class_2338; method_35231 getBlockPos + p 2 y + p 1 offsetX + p 3 offsetZ + m (I)I method_33939 getOffsetX + p 1 offset + m (Lnet/minecraft/class_1923;Lnet/minecraft/class_1923;)Ljava/util/stream/Stream; method_19281 stream + p 0 pos1 + p 1 pos2 + m (Lnet/minecraft/class_2338;)J method_37232 toLong + p 0 pos + m (I)I method_33941 getOffsetZ + p 1 offset + m ()I method_17888 getRegionRelativeZ + m (I)Lnet/minecraft/class_2338; method_33943 getCenterAtY + p 1 y + m ()I method_17886 getRegionZ + m ()I method_8327 getEndX + m (II)V + p 2 z + p 1 x + m ()I method_8329 getEndZ + m (J)I method_8332 getPackedZ + p 0 pos + m (Lnet/minecraft/class_1923;I)Ljava/util/stream/Stream; method_19280 stream + p 0 center + p 1 radius + m (Ljava/lang/Object;)Z equals equals + p 1 o \ No newline at end of file diff --git a/src/test/resources/projects/decompile/build.gradle b/src/test/resources/projects/decompile/build.gradle index 3715ad9b..103c1137 100644 --- a/src/test/resources/projects/decompile/build.gradle +++ b/src/test/resources/projects/decompile/build.gradle @@ -8,6 +8,13 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:0.11.7" } -tasks.named("genSourcesWithCfr") { - options.put("test", "value") +loom { + decompilers { + cfr { + options.put("test", "value") + } + fernflower { + options.put("test", "value") + } + } } \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/build.gradle.kts b/src/test/resources/projects/kotlin/build.gradle.kts index b248cb06..fca3185a 100644 --- a/src/test/resources/projects/kotlin/build.gradle.kts +++ b/src/test/resources/projects/kotlin/build.gradle.kts @@ -1,7 +1,8 @@ import java.util.Properties plugins { - kotlin("jvm") version "1.5.21" + kotlin("jvm") version "1.6.10" + kotlin("plugin.serialization") version "1.6.10" id("dev.architectury.loom") } @@ -15,6 +16,6 @@ version = "0.0.1" dependencies { minecraft(group = "com.mojang", name = "minecraft", version = "1.16.5") mappings(group = "net.fabricmc", name = "yarn", version = "1.16.5+build.5", classifier = "v2") - modImplementation("net.fabricmc:fabric-loader:0.11.2") - modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.6.3+kotlin.1.5.21") + modImplementation("net.fabricmc:fabric-loader:0.12.12") + modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.7.1+kotlin.1.6.10") } \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/ExampleSerializable.kt b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/ExampleSerializable.kt new file mode 100644 index 00000000..deff52c1 --- /dev/null +++ b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/ExampleSerializable.kt @@ -0,0 +1,9 @@ +package net.fabricmc.language.kotlin + +import kotlinx.serialization.Serializable + +import net.minecraft.util.Identifier + +@Serializable +data class ExampleSerializable(@Serializable(with = IdentifierSerializer::class) val identifier: Identifier, val test: Double) { +} \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/IdentifierSerializer.kt b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/IdentifierSerializer.kt new file mode 100644 index 00000000..07791a5e --- /dev/null +++ b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/IdentifierSerializer.kt @@ -0,0 +1,24 @@ +package net.fabricmc.language.kotlin + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor + +import net.minecraft.util.Identifier + +// Based on https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx-serialization-core/kotlinx.serialization.descriptors/-primitive-serial-descriptor.html +class IdentifierSerializer : KSerializer { + override val descriptor = + PrimitiveSerialDescriptor("net.fabricmc.language.kotlin.IdentifierSerializer", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Identifier { + val split = decoder.decodeString().split(':') + return Identifier(split[0], split[1]) + } + + override fun serialize(encoder: Encoder, value: Identifier) { + encoder.encodeString("${value.namespace}:${value.path}") + } +} \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/PosInChunk.kt b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/PosInChunk.kt new file mode 100644 index 00000000..78cb8f08 --- /dev/null +++ b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/PosInChunk.kt @@ -0,0 +1,10 @@ +package net.fabricmc.language.kotlin + +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.ChunkPos + +data class PosInChunk(val x: Int, val y: Int, val z: Int) { + constructor(blockPos: BlockPos) : this(blockPos.x and 15, blockPos.y, blockPos.z and 15) + + fun getBlockPos(chunkPos: ChunkPos) = BlockPos(chunkPos.startX + x, y, chunkPos.startZ + z) +} \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt index 5213187f..456ce7a9 100644 --- a/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt +++ b/src/test/resources/projects/kotlin/src/main/kotlin/net/fabricmc/language/kotlin/TestModClass.kt @@ -2,14 +2,22 @@ package net.fabricmc.language.kotlin import net.fabricmc.api.ModInitializer import org.apache.logging.log4j.LogManager +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +import net.minecraft.util.Identifier class TestModClass : ModInitializer { - val logger = LogManager.getFormatterLogger("KotlinLanguageTest") override fun onInitialize() { + val json = Json.encodeToString(ExampleSerializable(Identifier("kotlin:hello"), 12.0)) + val obj = Json.decodeFromString(json) + logger.info("**************************") logger.info("Hello from Kotlin TestModClass") + logger.info(json) + logger.info(obj) logger.info("**************************") } } \ No newline at end of file diff --git a/src/test/resources/projects/kotlin/src/main/resources/fabric.mod.json b/src/test/resources/projects/kotlin/src/main/resources/fabric.mod.json index 5618f96e..95d752c0 100644 --- a/src/test/resources/projects/kotlin/src/main/resources/fabric.mod.json +++ b/src/test/resources/projects/kotlin/src/main/resources/fabric.mod.json @@ -2,32 +2,24 @@ "schemaVersion": 1, "id": "modid", "version": "${version}", - "name": "Example Mod", "description": "This is an example description! Tell everyone what your mod is about!", - "authors": [ - "Me!" - ], "contact": { "homepage": "https://fabricmc.net/", "sources": "https://github.com/FabricMC/fabric-example-mod" }, - "license": "CC0-1.0", - "environment": "*", "entrypoints": { "main": [ - "net.fabricmc.language.kotlin.TestModClass" + { + "adapter": "kotlin", + "value": "net.fabricmc.language.kotlin.TestModClass" + } ] }, - "depends": { "fabricloader": ">=0.7.4", - "fabric": "*", "minecraft": "1.16.x" - }, - "suggests": { - "another-mod": "*" } } diff --git a/src/test/resources/projects/mixinApAutoRefmap/build.gradle b/src/test/resources/projects/mixinApAutoRefmap/build.gradle index df55bc40..e7ae0e90 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 {