From b533dacba8a3e9b64d4c6c6abfa99f2087236fca Mon Sep 17 00:00:00 2001 From: modmuss Date: Wed, 14 Aug 2024 10:59:18 +0100 Subject: [PATCH 1/4] Remove multi project optimisation (#1159) * Remove multi project optimisation * Fix build * Fix FAPI test --- .../fabricmc/loom/LoomGradleExtension.java | 8 -- .../mixin/AnnotationProcessorInvoker.java | 12 -- .../extension/LoomGradleExtensionImpl.java | 19 ---- .../loom/task/AbstractRemapJarTask.java | 7 -- .../loom/task/PrepareJarRemapTask.java | 105 ------------------ .../net/fabricmc/loom/task/RemapJarTask.java | 28 +---- .../task/service/TinyRemapperService.java | 21 +--- .../net/fabricmc/loom/util/Constants.java | 1 - .../test/benchmark/FabricAPIBenchmark.groovy | 2 - .../test/integration/FabricAPITest.groovy | 3 - .../test/util/GradleProjectTestTrait.groovy | 5 - 11 files changed, 5 insertions(+), 206 deletions(-) delete mode 100644 src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index 7d3d29f7..3c50c976 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -110,14 +110,6 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { void setRefreshDeps(boolean refreshDeps); - /** - * If true, multi-project optimisation mode is enabled. This mode makes builds with many Loom projects - * much faster by increasing sharing and disabling some functionality. - * - *

You can enable it by setting the Gradle property {@code fabric.loom.multiProjectOptimisation} to {@code true}. - */ - boolean multiProjectOptimisation(); - ListProperty getLibraryProcessors(); ListProperty getRemapperExtensions(); 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 f90f7e49..c1f93855 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -45,7 +45,6 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.extension.MixinExtension; -import net.fabricmc.loom.task.PrepareJarRemapTask; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.LoomVersions; @@ -121,11 +120,6 @@ public abstract class AnnotationProcessorInvoker { args.put("MSG_" + key, value); }); - if (loomExtension.multiProjectOptimisation()) { - // Ensure that all of the mixin mappings have been generated before we create the mixin mappings. - runBeforePrepare(project, task); - } - project.getLogger().debug("Outputting refmap to dir: " + getRefmapDestinationDir(task) + " for compile task: " + task); args.forEach((k, v) -> passArgument(task, k, v)); } catch (IOException e) { @@ -157,12 +151,6 @@ public abstract class AnnotationProcessorInvoker { } } - private void runBeforePrepare(Project project, Task compileTask) { - project.getGradle().allprojects(otherProject -> { - otherProject.getTasks().withType(PrepareJarRemapTask.class, prepareRemapTask -> prepareRemapTask.mustRunAfter(compileTask)); - }); - } - private static void checkPattern(String input, Pattern pattern) { final Matcher matcher = pattern.matcher(input); diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java index 139bd39f..c32c472e 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java @@ -39,7 +39,6 @@ import org.gradle.api.configuration.BuildFeatures; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; import org.gradle.api.provider.ListProperty; -import org.gradle.api.provider.Provider; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; @@ -55,10 +54,8 @@ import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.library.LibraryProcessorManager; import net.fabricmc.loom.configuration.providers.minecraft.mapped.IntermediaryMinecraftProvider; import net.fabricmc.loom.configuration.providers.minecraft.mapped.NamedMinecraftProvider; -import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.download.Download; import net.fabricmc.loom.util.download.DownloadBuilder; -import net.fabricmc.loom.util.gradle.GradleUtils; public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl implements LoomGradleExtension { private final Project project; @@ -75,7 +72,6 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl private IntermediaryMinecraftProvider intermediaryMinecraftProvider; private InstallerData installerData; private boolean refreshDeps; - private final Provider multiProjectOptimisation; private final ListProperty libraryProcessorFactories; private final LoomProblemReporter problemReporter; private final boolean configurationCacheActive; @@ -103,7 +99,6 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl }); refreshDeps = manualRefreshDeps(); - multiProjectOptimisation = GradleUtils.getBooleanPropertyProvider(project, Constants.Properties.MULTI_PROJECT_OPTIMISATION); libraryProcessorFactories = project.getObjects().listProperty(LibraryProcessorManager.LibraryProcessorFactory.class); libraryProcessorFactories.addAll(LibraryProcessorManager.DEFAULT_LIBRARY_PROCESSORS); libraryProcessorFactories.finalizeValueOnRead(); @@ -111,15 +106,6 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl configurationCacheActive = getBuildFeatures().getConfigurationCache().getActive().get(); isolatedProjectsActive = getBuildFeatures().getIsolatedProjects().getActive().get(); - // Fundamentally impossible to support multi-project optimisation with the configuration cache and/or isolated projects. - if (multiProjectOptimisation.get() && configurationCacheActive) { - throw new UnsupportedOperationException("Multi-project optimisation is not supported with the configuration cache"); - } - - if (multiProjectOptimisation.get() && isolatedProjectsActive) { - throw new UnsupportedOperationException("Isolated projects are not supported with multi-project optimisation"); - } - if (configurationCacheActive) { project.getLogger().warn("Loom support for the Gradle configuration cache is highly experimental and may not work as expected. Please report any issues you encounter."); } @@ -274,11 +260,6 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl this.refreshDeps = refreshDeps; } - @Override - public boolean multiProjectOptimisation() { - return multiProjectOptimisation.getOrElse(false); - } - @Override public ListProperty getLibraryProcessors() { return libraryProcessorFactories; diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java index e0f2abf1..12f5511a 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java @@ -83,12 +83,6 @@ public abstract class AbstractRemapJarTask extends Jar { @Input public abstract Property getTargetNamespace(); - /** - * When enabled the TinyRemapperService will not be shared across sub projects. - */ - @Input - public abstract Property getRemapperIsolation(); - @Inject protected abstract WorkerExecutor getWorkerExecutor(); @@ -116,7 +110,6 @@ public abstract class AbstractRemapJarTask extends Jar { public AbstractRemapJarTask() { getSourceNamespace().convention(MappingsNamespace.NAMED.toString()).finalizeValueOnRead(); getTargetNamespace().convention(MappingsNamespace.INTERMEDIARY.toString()).finalizeValueOnRead(); - getRemapperIsolation().convention(false).finalizeValueOnRead(); getIncludesClientOnlyClasses().convention(false).finalizeValueOnRead(); getJarType().finalizeValueOnRead(); diff --git a/src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java b/src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java deleted file mode 100644 index a774ac25..00000000 --- a/src/main/java/net/fabricmc/loom/task/PrepareJarRemapTask.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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; - -/** - * 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().withType(PrepareJarRemapTask.class, 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(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 Path inputFile = getParameters().getInputFile().getAsFile().get().toPath(); - prepare(tinyRemapperService, inputFile); - } - } - - static void prepare(TinyRemapperService tinyRemapperService, Path inputFile) { - tinyRemapperService.getTinyRemapperForInputs().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 d986c6ed..0061af65 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -117,10 +117,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { getUseMixinAP().set(LoomGradleExtension.get(getProject()).getMixin().getUseLegacyMixinAp()); - if (getLoomExtension().multiProjectOptimisation()) { - setupPreparationTask(); - } - // Make outputs reproducible by default setReproducibleFileOrder(true); setPreserveFileTimestamps(false); @@ -128,19 +124,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { getJarType().set("classes"); } - 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() - .withType(PrepareJarRemapTask.class) - .configureEach(this::mustRunAfter); - }); - } - @TaskAction public void run() { submitWork(RemapAction.class, params -> { @@ -152,8 +135,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { params.getTinyRemapperBuildServiceUuid().set(UnsafeWorkQueueHelper.create(getTinyRemapperService())); params.getRemapClasspath().from(getClasspath()); - params.getMultiProjectOptimisation().set(getLoomExtension().multiProjectOptimisation()); - final boolean mixinAp = getUseMixinAP().get(); params.getUseMixinExtension().set(!mixinAp); @@ -209,7 +190,6 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { ConfigurableFileCollection getRemapClasspath(); Property getUseMixinExtension(); - Property getMultiProjectOptimisation(); Property getOptimizeFmj(); record RefmapData(List mixinConfigs, String refmapName) implements Serializable { } @@ -235,9 +215,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { try { LOGGER.info("Remapping {} to {}", inputFile, outputFile); - if (!getParameters().getMultiProjectOptimisation().getOrElse(false)) { - prepare(); - } + prepare(); if (tinyRemapperService != null) { tinyRemapper = tinyRemapperService.getTinyRemapperForRemapping(); @@ -261,7 +239,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { optimizeFMJ(); } - if (tinyRemapperService != null && !getParameters().getMultiProjectOptimisation().get()) { + if (tinyRemapperService != null) { tinyRemapperService.close(); } @@ -281,7 +259,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { final Path inputFile = getParameters().getInputFile().getAsFile().get().toPath(); if (tinyRemapperService != null) { - PrepareJarRemapTask.prepare(tinyRemapperService, inputFile); + tinyRemapperService.getTinyRemapperForInputs().readInputsAsync(tinyRemapperService.getOrCreateTag(inputFile), inputFile); } } diff --git a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java index 4d2c4098..7c0697cd 100644 --- a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -46,7 +46,6 @@ import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.build.mixin.AnnotationProcessorInvoker; import net.fabricmc.loom.extension.RemapperExtensionHolder; import net.fabricmc.loom.task.AbstractRemapJarTask; @@ -70,7 +69,6 @@ public class TinyRemapperService implements SharedService { final LoomGradleExtension extension = LoomGradleExtension.get(project); final boolean legacyMixin = extension.getMixin().getUseLegacyMixinAp().get(); final @Nullable KotlinClasspathService kotlinClasspathService = KotlinClasspathService.getOrCreateIfRequired(serviceManager, project); - boolean multiProjectOptimisation = extension.multiProjectOptimisation(); // 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 var joiner = new StringJoiner(":"); @@ -81,9 +79,8 @@ public class TinyRemapperService implements SharedService { joiner.add("kotlin-" + kotlinClasspathService.version()); } - if (remapJarTask.getRemapperIsolation().get() || !multiProjectOptimisation) { - joiner.add(project.getPath()); - } + // TODO remove this when removing shared service manager. + joiner.add(project.getPath()); extension.getKnownIndyBsms().get().stream().sorted().forEach(joiner::add); @@ -103,20 +100,6 @@ public class TinyRemapperService implements SharedService { final ConfigurationContainer configurations = project.getConfigurations(); ConfigurableFileCollection excludedMinecraftJars = project.files(); - // Exclude none root minecraft jars. - if (multiProjectOptimisation && !extension.isRootProject()) { - MappingsNamespace mappingsNamespace = MappingsNamespace.of(from); - - if (mappingsNamespace != null) { - for (Path minecraftJar : extension.getMinecraftJars(mappingsNamespace)) { - excludedMinecraftJars.from(minecraftJar.toFile()); - } - } else { - // None fatal as this is a performance optimisation. - project.getLogger().warn("Unable to find minecraft jar for namespace {}", from); - } - } - List classPath = remapJarTask.getClasspath() .minus(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)) .minus(configurations.getByName(Constants.Configurations.MINECRAFT_RUNTIME_LIBRARIES)) diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index 05798ebe..4d192ab2 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -128,7 +128,6 @@ public class Constants { } public static final class Properties { - public static final String MULTI_PROJECT_OPTIMISATION = "fabric.loom.multiProjectOptimisation"; public static final String DONT_REMAP = "fabric.loom.dontRemap"; public static final String DISABLE_REMAPPED_VARIANTS = "fabric.loom.disableRemappedVariants"; public static final String DISABLE_PROJECT_DEPENDENT_MODS = "fabric.loom.disableProjectDependentMods"; diff --git a/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy b/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy index 6857224d..9be4cdbb 100644 --- a/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/benchmark/FabricAPIBenchmark.groovy @@ -48,8 +48,6 @@ class FabricAPIBenchmark implements GradleProjectTestTrait { patch: "fabric_api" ) - gradle.enableMultiProjectOptimisation() - if (!gradle.buildGradle.text.contains("loom.mixin.useLegacyMixinAp")) { gradle.buildGradle << """ allprojects { diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy index 8d94b6aa..a116bfcc 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy @@ -49,8 +49,6 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait { patch: "fabric_api" ) - gradle.enableMultiProjectOptimisation() - // Disable the mixin ap if needed. Fabric API is a large enough test project to see if something breaks. if (disableMixinAp) { gradle.buildGradle << """ @@ -103,7 +101,6 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait { then: result.task(":build").outcome == SUCCESS - result.task(":prepareRemapJar").outcome == SUCCESS def biomeApiJar = new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/999.0.0/fabric-biome-api-v1-999.0.0.jar") new File(gradle.mavenLocalDir, "net/fabricmc/fabric-api/fabric-biome-api-v1/999.0.0/fabric-biome-api-v1-999.0.0-sources.jar").exists() 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 9cf2e7b9..9356a8a5 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy @@ -33,7 +33,6 @@ import org.gradle.util.GradleVersion import spock.lang.Shared import net.fabricmc.loom.test.LoomTestConstants -import net.fabricmc.loom.util.Constants import net.fabricmc.loom.util.ZipUtils trait GradleProjectTestTrait { @@ -332,9 +331,5 @@ trait GradleProjectTestTrait { } """ } - - void enableMultiProjectOptimisation() { - getGradleProperties() << "\n${Constants.Properties.MULTI_PROJECT_OPTIMISATION}=true" - } } } \ No newline at end of file From efe66075ba1caf790dc6e983f7347cc4ef423201 Mon Sep 17 00:00:00 2001 From: Juuz <6596629+Juuxel@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:28:37 +0300 Subject: [PATCH 2/4] Include dev jars from other projects in classpath groups (#1155) * Include dev jars from other projects in classpath groups * Verify that projects use Loom before getting namedElements --- .../loom/util/gradle/SourceSetHelper.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java index d17b3cbc..209703d6 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2022 FabricMC + * Copyright (c) 2022-2024 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 @@ -41,6 +41,7 @@ import javax.xml.xpath.XPathFactory; import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; @@ -53,6 +54,7 @@ import org.xml.sax.InputSource; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.ModSettings; import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; +import net.fabricmc.loom.util.Constants; public final class SourceSetHelper { @VisibleForTesting @@ -123,7 +125,7 @@ public final class SourceSetHelper { } public static List getClasspath(SourceSetReference reference, Project project) { - final List classpath = getGradleClasspath(reference); + final List classpath = getGradleClasspath(reference, project); classpath.addAll(getIdeaClasspath(reference, project)); classpath.addAll(getEclipseClasspath(reference, project)); @@ -132,7 +134,7 @@ public final class SourceSetHelper { return classpath; } - private static List getGradleClasspath(SourceSetReference reference) { + private static List getGradleClasspath(SourceSetReference reference, Project project) { final SourceSetOutput output = reference.sourceSet().getOutput(); final File resources = output.getResourcesDir(); @@ -144,6 +146,19 @@ public final class SourceSetHelper { classpath.add(resources); } + // Add dev jars from dependency projects if the source set is "main". + if (SourceSet.MAIN_SOURCE_SET_NAME.equals(reference.sourceSet().getName()) && !reference.project().getPath().equals(project.getPath()) + && GradleUtils.isLoomProject(reference.project())) { + final Configuration namedElements = reference.project().getConfigurations().getByName(Constants.Configurations.NAMED_ELEMENTS); + + // Note: We're not looking at the artifacts from configuration variants. It's probably not needed + // (certainly not with Loom's setup), but technically someone could add child variants that add additional + // dev jars that wouldn't be picked up by this. + for (File artifact : namedElements.getOutgoing().getArtifacts().getFiles()) { + classpath.add(artifact); + } + } + return classpath; } From 4fef15688861ca0f776d406e187356f4af87f497 Mon Sep 17 00:00:00 2001 From: modmuss Date: Thu, 15 Aug 2024 14:29:18 +0100 Subject: [PATCH 3/4] Update to Gradle 8.10 (#1157) * Update to Gradle 8.10 * 8.10 for real --- .../bootstrap/LoomGradlePluginBootstrap.java | 2 +- build.gradle | 7 ++-- gradle/libs.versions.toml | 2 +- gradle/test.libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 ++- gradlew.bat | 2 ++ .../buildSrc/stopDaemon/TestPlugin.groovy | 31 ++++-------------- 9 files changed, 22 insertions(+), 31 deletions(-) diff --git a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java b/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java index 976517bb..332325f2 100644 --- a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java +++ b/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java @@ -14,7 +14,7 @@ import org.gradle.util.GradleVersion; */ @SuppressWarnings("unused") public class LoomGradlePluginBootstrap implements Plugin { - private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.8"; + private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.10"; private static final int MIN_SUPPORTED_MAJOR_JAVA_VERSION = 17; private static final int MIN_SUPPORTED_MAJOR_IDEA_VERSION = 2021; diff --git a/build.gradle b/build.gradle index 9b9ec4ed..a7ea0897 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,11 @@ if (ENV.BUILD_NUMBER) { } // We must build against the version of Kotlin Gradle ships with. -def kotlinVersion = KotlinDslVersion.current().getKotlinVersion() +def props = new Properties() +Project.class.getClassLoader().getResource("gradle-kotlin-dsl-versions.properties").openStream().withCloseable { + props.load(it) +} +def kotlinVersion = props.getProperty("kotlin") if (libs.versions.kotlin.get() != kotlinVersion) { throw new IllegalStateException("Requires Kotlin version: ${kotlinVersion}") } @@ -306,7 +310,6 @@ tasks.withType(Test).configureEach { import org.gradle.api.internal.artifacts.configurations.ConfigurationRoles -import org.gradle.launcher.cli.KotlinDslVersion import org.gradle.util.GradleVersion import org.w3c.dom.Document import org.w3c.dom.Element diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 47e1626d..940a8751 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.9.22" +kotlin = "1.9.24" asm = "9.6" commons-io = "2.15.1" gson = "2.10.1" diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index 33eaef18..1dfbf935 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -6,7 +6,7 @@ mockito = "5.12.0" java-debug = "0.52.0" mixin = "0.12.5+mixin.0.8.5" -gradle-nightly = "8.10-20240613003017+0000" +gradle-nightly = "8.11-20240814172604+0000" fabric-loader = "0.15.11" fabric-installer = "1.0.1" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 12612 zcmY+pRa6|n(lttO3GVLh?(Xh3xVuAe26uONcL=V5;I6?T_zdn2`Oi5I_gl9gx~lft zRjVKRp?B~8Wyrx5$mS3|py!Njy{0Wt4i%@s8v88pK z6fPNA45)|*9+*w5kcg$o)}2g}%JfXe6l9ig4T8ia3Hlw#3f^fAKW63%<~GZJd-0YA z9YjleCs~#Y?V+`#nr+49hhsr$K$k!lg}AZDw@>2j=f7t~5IW6#K|lAX7|^N}lJ)I!km`nrwx> z))1Es16__aXGVzQM0EC8xH+O!nqTFBg9Ci{NwRK*CP<6s`Gq(~#lqb(zOlh6ZDBK* zr$|NDj^s6VanrKa+QC;5>twePaexqRI%RO~OY075y?NN90I|f^(P# zF=b>fZ73b5JzD`#GC3lTQ_B3lMeBWgQUGYnFw*HQC}^z{$6G4j(n4y-pRxPT(d2Wgb%vCH(?+t&Pj z)QM`zc`U`+<~D+9E{4Uj2kc#*6eZMU$4Oj6QMfA^K!rbl`iBix=2sPrs7j@aqIrE zTaZJ2M09>rp$mgyUZ!r2$UK{+DGqgl`n;*qFF~M(r#eh`T{MO?2&j?xgr8FU$u3-` zhRDc_I23LL4)K&xg$^&l-W=!Jp-P(_Ie07q>Je;QLxi8LaEc%;WIacJD_T69egF?7 z;I_Sg_!+qrur8$Hq4grigaiVF>U7uWJ@Hkd&%kmFnQN-P^fq0gB1|uRt!U#X;DnlV zo?yHWTw7g5B;#xxY`adhi4yZn@f(7-Xa(J6S=#d@&rlFw!qfvholE>MEb|VWn^g}G zMSrK&zQ^vDId&ojL!{%{o7?s{7;{+u%L{|tar(gp?Uxq3p?xAysB>0E$eG#$tvkk9 z2Q2gEP17{U6@UD*v({5MP-CTZfvWMItVjb4c;i~WLq&{?Q1(koX&vt7+$z}10{^Id z{KDjGi0JpD7@;~odF__0m|p;5rIrHidOP9^mwKe#-&JX-X@acc)06G{LO1Wu)#gvZ za~y9(fhA%UwkDOVU1LBJ`0ROE z4&)dJKK%mG@+CIm?+wt9f~@xIMr8}UH*K1j| z0pppo{7gv3v{URwxVMeg>Ps!L5IKxm zjac2egjgb0vH5i75$s|sY_RYec#>faqJk|AGgV;v=^%BM(^p{p;(^SVt-88G9f!q; z>p}9E4^f0=01S2pQBE4}9YqE%TV)*hlU^8k9{&=K76+*Ax^r=AkBb%OCP^P2nm0Ri z;D-|Zk?gGeU<12ti2CnPVNA(Pb)02+r|&yTWW-OJO7 zNLb0pps6aN?A~NJp5kj{{IOlf!5KWMleV@-hYLift)D>-7K+tgs=7Ake}oBnIy-y1 z(Hn@Hjw=_(x>dO5ysQsrnE%A*bk0K<-j{1Yqz@#n#jOL^AzCr#wR|WYzqk6i7v)Lf zkXdKxzuu20aP{Tbg$(+9&oh7cd(Uoqqf<#ujb$q4sZ~gxFbQfS zS)kNklyL*{2AELgjZ(LBu*>S(oH5AaJ;YiB@;l@=O%F6B?oanzoYRM^fQ9-<~^=3$H0g^JPMLQo@SZ@QuNvy)tyJ)LSj`+()#fy?{aV4Yg^7dlQ7AQM^3GLCR2dAFR zJjtfKiVqF`l-H_fz0HD|9g>)pOxn}k!vdZ=DO!7Sikm{Z%P6BrRkBS6W?ZB5W&7rT z@uYpf@M@a!z7H&o@-yrcCL^Ff3e7p3T`R9p?@o-acXmbTSa0>ZANzCSgovsd%;i$| zVus`not!oL#(W`L-!9w0jdaECaG4hk{V7IOs676ZquZH~0TX5hDq|)x z6T497l|E?f4)LA>j=S8}b$0LS=I4h|hUFJYJODT8Li@#6kF$k0)@*l{RnM1HQ%?VT ze-Pqlc!~t(oumVC*?5fwR;P6u{tHaZ~*LlD;B)4f? z?lpWfa2P@)g57flVl83Ej%P`2)gGyaPjhvD(%i~{`2b>#3!+y&` z!2nuwHMFA-zUY}f1^0B8<`N)Gr=A4TS@b1qykmd0Pq{?r)+1^^+D(=xasb^Tf!oK9 zBLL+*p6M_#ufgLzgq1zcSwZsZnQWFLC3`Yxdg-2=*tT`J9nrfYt)RF)YryBf8_gW{ zvKbB+oZLehfT)S#<|y1)E0hW^?+AnqPXq9Hu;v3dsMGdr{SVyF63;K<8VcgI#~}1i zLYSBL0K;RTT(;>2x=*!1Di9w0mwr;`CN}kM65|Ay{~z}_^JKOsRaN<~#9O^iiW<5P zYN7r~HV!#Nz~IZU`P>1Xe%4f~K}KcF#X&5kO*G}-)74S*tQ8CietdPcA1Yl;S=Mr# z`#MYY!{s^uo=jn7;k6O%(}fN+*0cWMpt~#n9DR<3NyU?+3D^AgI}S)Cu-Tljg`VY} zX1=fq$?8$DtOeGxE6f8lbS_6Q3C4+LDTO$}_IpM$Xv<|QSC%+Oll^q$y`7o@jD{dp zNDl|&X)r7wETa-#h*d`KXntxI(Y{vLha{$0i7@G8xx^m=c<{lJ9?p-i!^W{%j7-oo z0W^SzZ^(Wkyz*We{lEn%Yhu-ycUOHtrRiVJL4~&S91*D0MrLu}Q>v-Mc?GcWfpyz% zX|UvcN@krFO#@v|CtYM}g|=L3%aMo$E5<@CM%c*;?u>LOTz00@+dt1{yg1y=$h+{|D17U}$*^fE^H&8b431EUE z<9tv0V_#%#&1N#j7AKCj!tTK@J%oFW*ESW<(#Gl#Xs%v<@AitI?s92nLzm<)w3Wkkom1f$gcdUi%g_*jofy&}N#luL<$GVIe{iQkQ)sIHVy zBgItnPBFamrv6Kb{eE($Q(f`ZPeW!Hm%Y@F*OF1sKB{Yy|C>WEv_mfvv-N-jh)B-5 z4a!1WcT@9a+hGaBrc~sz=>G?Q!*Zp^JFRUvBMyNR1;`)j$RhH$6gEyVKhd$&K-CFT zXaWC-Y=fyOnqT84iMn9o5oLEOI(_3fk!W^8-74|q1QhQ|CmT0i=b;6Z3u?E{p7V{? z;f#Q-33!L+4&QQcZ~GAqu$NS{M;u%`+#9=7^Oa5PKvCCCWNG_~l(CidS!+xr-*gg{ z$UQ`_1tLT_9jB=Hckkwu>G{s0b0F4bnR7GibmHo?>TR&<3?D;5Fb#gd8*wYa$$~ar z7epl1qM)L{kwiNjQk}?)CFpNTd?0wAOUZ|gC{Ub|c-7h~+Rm(JbdoRe!RNVBQi!M8 z+~U6E2X&KSA*T6KJvsqwqZl#1&==Dm(#b^&VAKQ>7ygv*Fyr;)q9*^F@dCTg2g!w~ z%hg)UXAUyIpIbLXJv1nZX+a_C)BOH2hUim|>=JHCRf(!dtTidb&*~I!JrfRe+PO>w z@ox$G2a3i9d_N9J=|2$y2m-P&#PTNwe!oLBZFs;z|F5kXvBDn<)WwE0E3$ow=zg3R zK(9;sf0t;VEV3@gAg7jRtnj%-6O@!Hvg*;XcUAw}!=2*aErvB(eQIm(-UGmq^J=XN zTqJo$Y|WKo^HlBF3BXJrA#}7ZLg=r*w`I*~Ix`o&2k8^(0mt8Rp=A>F`&gehhp@Jy z^e^#B2!~$LvNCKugg)8)-G%&THdk~kfextilegP9?#C#()F59U$&eo(h|5>ceo*Em z{PEE79T$YP|Kr7K`WBHbtQwyxFkCl6xX&+oUf90B5xoi3_5KHHCyEE*oPbOQkfMz& z6^hT8_NXd2iWk{q9IKae1{_7hMPH8I7_BMtVOM4 z6jm?E0QJOn$qrgsJ`9w##GB9?G})-GXSQo6(tYS(Q0-Ct$co?Zzl0?NHsDRron?;_ zZZgQg)%XW>P?8_&zoGuF(>Och2kEJXsu1_X&~w87x!b z>~h!a>e7{`p@+#hXF88wI*JeWRZ;J4ev4<}HWf|Z;(7$E!S5l9wzBHFe>^I{2`a;a)QnAwa2xv1e(bq$<}!8o^ofGvYpk7dBR+`*%iE;hUY5 zaHF}OjGO9r*{%lmcK^uFiTHgoUD`^9Nx@~;Bg!V* zuuJ&ti{DQiq7RyJAR94wem{}cPK1J(Yxnn_{=>?USqz-~&QXRStS^s-7TksZ$AEI! z#og36s3JGtGU{CnDHRFtipFqvrE*gw7_K@NN0h+ItTq@4fqN!HeQU1y7*X?9+IfZT4Vxebpt z%#VzgdDK~-&+=Z*#>=n#XUhNvBZp3=Cr41jMqwJkHLf3L7Vm~V#GgJ(Jpii~PmJ#s zA7Ft!{xD@z>9DUb4JbiUBdNEcU4BO$651iN*mp*f)HbRRM`Cx5cR?5IfEcU{IZWwf zz(M6CDv)>xa3x}K6%tP^i15P1&&DOLK=k~+jNR$UK3frSl+|PjSC-dBItvD~LL! z>_g(YYdO4k(5EbPOw+v+;G7~jYm>F@Ai|o`gs%F)F8tDz$dl7Q%aCe|v|$UkAul_R zNlA-beBX^IJU?kgS`E$it7nF4DaI!SJAGq)2P&Few(-|tp z?K+%D3e4{pfkayrcbm0ftu6Ol2ZzdKM+4i!hNP3NRL`EvvZJ3yvNr2MV%igZ4kj``Qrdb_OI$7jWP z;l0DYf&0(-*QcP5zrP`HVznW+SbH63Qx$7_9~NjRNg7eKqI!UJ=XH`g^=t8GiFTu( z?2L{JKEu%jJx&XjNzU(*!ZNmL1@RlJA0G$2_LrAb_7lmjil(GSlSM zwTes`m+3R;3#N~Xg#9owh3ycXV8@ZlaY_16kpPFA={721b~URO4HD3sp%fmkZM}k) zZB0#)kP=RkNB~R-MCk8aljG_bagt4vIb~8)BV%(b8_;)&Kf9GX+%O_cNG|(D$!3&D zL(I8}*LqN5NntipFlN13=`D>6!{D@CFMBH0kW3=HccJV+xW~|$qeFR5i-2{X+iWMu zI2$gepQ)H_B%ip_BlWOQ*|pErXs|4ir{IHccgaIJ84irE{?+$KDABXr&f`jB^V-c% z$$u`uU1YB^{<+UN2cNg#7&0bz@yF?5>j|;)5&IV3wIQp58X#OE-M^$HdyvL|Um5t? zhZlAG!Mz%XkUe3t471JM*Yur}o30vzu6RN7gJyNcf!IItsDO730mcJ*O!~V``y5=3 zNJGp34DZ}wd1H6V`Uuy%es>BiO_aE-S8jzir#$& zyk)@2a5tP$@g%jW^b^JGdo)X@Q%sE`^lDQmY9m%uDFpPX`w9%=yQ+nneMm#OaXcD` z9}{tn5A2b2z9783vL2_jSao?uxJhWJoq%47*RafM4o0@gY(p)F>qT4^XM5GLzV#6j zC+HoGhAne7o_w{WUo(B++z7lU3Y0k1rYv9|TSv0vR-Du(5=VakbbelgZTeDn+a_Wv zq_j-^+Qz1WAl;Zg>ahX|CERbX1V%B!hTKN?M}fGoA07M(WU&NfT&TmN`P@56U2 z^)vLDs|Ln~0iTtn-?KTeQl@T&bskJFuTUS!m+$CS9vnd}8(UMO|Kv6TCfGN9NUu&4 zL{)GTxPq>fwsJ~aU=4Qhuq8*RzDsP(LZh$BHezq&9gK$IS<|DYbm})$QTGCS6T;Dr zEkLct!b+#<1r9OKG@P!f1wm8>=Nz!7OzJm!g<+`?N3;YaA3(P@EL=(sTaRMDD!c8=-XN^4BXp(eVkj$NmEMYPP>YJ4bJ3yUud z<3BeJAJ$6z^TuywnfH5lv#$lgwraNw{IV=tIznPH1DT`v-5yS=!)J<}xxl}uZf9azA2A97Haf!;<3y01hlw?dWNEv@TLi1s-mO4vmIT%O_42nS z$VRWrs9NngqRRkWAnWkn%`Rw@?wH|)7XL`EL5EZu$qyJW31&CB^T_)qwIv!{;E_6 zo-9XAryQRlk-O0>o#-SZO>|6OYq;}<*>Wu1AsVRiXY4f8qb;+sItv3AyS!4Ry+q}) zA!pAB|BmC;=RIOk^^vlsEH(!Q!7_1FK~ZB2err*o!+b(r=m1b?$6d!%zmN+69LXnT z&gRmM+n_R-F@sT*IYv0_mGPvur!u`iWbQO7SqiGFLeY&yga zf`lM&B74FA2C?N@8_z652fjhBEoDUKbP8hL{0{HAF%qDo7)o3=3rg#6)T7%%5^wl% z9R0*S*<~>nzYOdQk2l`9h#t+gJy_xujw6xjV(8S<_DbVg61&pT%Hi42l%D73G?adn znB%UdNM0p}lEF-P2%TAMam2zpQev71e>a$$%i+r~b+D9G9pF|oY_*(-u*89oKsXLY+UIbqq)MQ%(GYS{(*n_S_*RN$*~`zUtab%0aKwhx znc)Yo?{xq1sJCgQD)TeTci1ucvbez9q=A72H(-SB18Kl&6^vHV8^i!p@>iF!DIw17 z+8Q)TNisB7>pwyww4y)yJx*wX6SJO78eLBC-ar1+k$Z9fy;wBD|3kzI{<+l*>PSY^ z_?nLOZaeWbU@C3hfK?X;Di*8CHCPkx2qco6(ZyJdqSzp^TJ_5Lpa0UP{Gy+!b0Lr% z@xYxSjUKoY6L#>$qx~KD$-0=|OF7zhVP~ntMgEALYPIfhj@+ z!;JJ7te>CcovruwHsJH6Lta$nm|%^C@=V-rmhU{+I~0(|XHQ9jt@L7pb{gx#{4r!) zg($FyFTslcgu(~6lYr$nW?)%*l#VJ=R-jxK(x=t1bWlu(nL66T#qj%3aZ@uVhy}Co zDU_q61DD5FqqJ*#c|(M5tV)XBN?Ac^12*q)VN4yKPJ|#==S_`_QD9|0ls!`2)SwuHDRA_OfXQDq3%qW&MZB}Z!=k-9xqev8jHz(H z{^D@cIB~QiK>~wa)A&^Ll^Wi6QgCzU;iv-BHsLBs zH7=jN%|>0S`SjP%M&AF1PNVDp_FZ?2Bm@7`DC&v(pYrw!!yD#4 z6+<=HS0Ln6MhoKxF<%~H`y20{vf#pxh=;j{zY381gvAFekgG|>G1zo8$&az{V=;JR zy_puF4$L$?EMhT?;TpQoR*j16ll`#AS4e96C}yp_aGKkBe?1H|k_;gG-~Xorc<;lI zkB}fB{$c-D2mGA&{rm<*@F5)c3X+6??g~XoEwuzSuch0D@W~P5(2I8v8F$c2$Vw51 zP#YLSBDqtWW^EYBl^QYHF+MA7am6f4DOhwnJM=W9$uvMOsZ%_~?)2C#wb?CkI$7{K zEi)=#|5pFvg^){zK5kpBLjB2kZ+$ZB|L=W|aNwyyb(gC2l7bcpx{E-H@)q6@D6N^xh`{1E%ItF2$eeB_SjI@b2WgTpS1thwg&n`jiIzw^TtXUyB{00($GIq>vbj|}bav}}Q_~wp3>k8!E@hVC;OMUTu|= zAy#vXH*GrUHu7^cNZWe1>y;2(51js9wbu+R3Aa*(wzH9+X0dIsf&gc_x|_LP z>~CF^?(~U}+l~ehe|i>?4eo!xkq&Lk+RR-1duNP#o~>@1x)s&i&u zRaYL@+D&_M|JLI6fHbEr_`U;HgPTh#E3?sB)A$*gqyBgg*ql|a-m*TX5rACbWKCE6 zdeQ`v8m6>g^ugv`p|HY^#1QZrGGUj0^HVDc@{?Q0yhalbBEV{+|HzC^-{&e{5K%z9 z6Bxtnfu1!@Mp+Q&*&~;FOg&*Vm<@4b;{FG0-!UUXX!|)1w}op!B_|7_s~d(+=9Gba zKp8`LaB4D(H=cGcspJ_TjYaOwMb=sGn^gtUVhK!UI~2KKYEE-NC}F>+BEY7IVvy%KRvm00tg!Q`y=er}wpEetX}K@;}(}{s9AzV#q2@ zBy7}->|N?13POrs`;U?(qAG(I$~Gt+Rgw%aNZ_0fs_utVvRJT-7z4!@x36v@=NBX=IqkK{#Kg0w48de@?#Yb4M(Svj5=T+<ONr8-oh7l?Cji@+erqur zFhZ=9|Lk=$`c}v4u`)-!!UI=!9Jo@h&7p4RlS#u! zZ7-prn75JkV?VjptX;@$#`U`{vB!=Z?V`T*FBF>J?vsML7e6@2GbUteMFfX-TUu{2 zLNIG*;dV)8GV8gAgEf#)X3A>p3^CRka1v?~8x^anBhQ=L=LsOl=&pcOYHo98m##ye z34MtGCDK!`ptl?taGMr5q{!zVc? zG00e){TV?`YA9eB;(lA3lXI?RrB4BYQGk?vOmTIUJED=(`_*gtn2DB-t4WW54as*W zb2kD-lWX>lb$+W!VFakki>B^Vc+u$?NLF>)!U%b@Y}gYJ>m2H=^x0=nsE0TF^Yu0h ztgH8-o1%+jCk(+&`|)tTfEVHq0cMeFa{Uz)X$;fCq%Y=SOWML6bYfeP8j5hktL`KK z(18`XrUn&WN9PtFxh&dX`y~YBsmdhi7Kw%tKzM%^VEhdD<_XkulW-x=JN6OPbFI4@ zzDDRN+f=@{0h*MswwOqG6gJ?{NuHx(y-|FUGsxyZ*x0~$MW(eY>vqq4Fh#t7uzw=- zKB?|!0N~!h^AMdLa)oR!Ca#HZ9&Zf)ghuO<^RN)4twRlygHnQG(BE{cDc5E}OF4;xss6gYyV~EcJvJkX)xNWb=@yw!uq0v-sf^rvkp-;?DPWK@*SEw|V;IH=7 zfQqEV_>DjOPT~8X*J|H8=&RnzK4~S7ML~nLX^%s-Vqc^aWy7N$y57qciZGcqy#=zU zs8hcHiI=D$+RB{|62{ohCTiaML6FI4Uhzo5D{Jik@poCs0w7F)*w}F4r0sJ~#u-72 z5bK=ANt=M$Dh5NKnxGsg9NRR?WD-x|FhTwBjd zD<-K>44DB~i%frJOfnzh1R>PRY34kw!6~p3M$JLaD1r@`=h)~Ngks-(gdXh^Q?BTP zZ^Zj5w1AwtuR2$~E7s9iZdF}z%pv1em^V2rM{1tLUY@-+Sc0(9jA|iZWml1;v13=U zHf?y@#mb--7z6$ue>`qjhE~brk$AY-RG90~5wcBbDReXR2)pKg{L>;H(DI`U!MLNQ zY9rFJP@ZQ}jlcMh%WSCo%vf+nd0Gmd*F%KMIe>slCUh)8Ma|;M_I+v#;|ueg9oLg; zq2HtZX%&#F7vdpNlkX?}(C7dGC^y#NB#m4%69RzTNrk%4ol~hSI%>2r6B|*ZkW(*P z;u#s;+faHo{tfy+1L^RzWDi*^JR0iY(zJDB36y_QJ+|E-2x+cY z!V8uLNktH~q>WQZuY!Ap66WP|E!0PA1jK~)^8oJVGbspJs6QL!!-5Qm7 zHYI|_`Actg?vDzdg5{86w@GS$G6ANzff7->6i5pB$T4O}`fZ_;{217Om0gN5zTr12 z5mW{hCzCE-QubjxN$TAE-XgI-8dTY@OZmq`y+y_>dk*(qXF0{nam|q@~i}Utp*k{yurq(DW54hkDT4bbg z=_etM?Nf5W^o-HEu9_?&xEqPg^P^mTxLH8n%u$!mWvFG|{&)jtnU&6|5-`~eaNz0%D1BDo`{ zS1N5(KW5v^2eLdd_%`uaRndF@h0Uo6=M|8?b~KbOLZk{HXEnGmtgZXf2inI*1r%n! zQ3&%RI4r{f&dwW~HwH0Ked9b!k6{>_19H z_Ai>5IChDMY(FfMyG%;30?SQ{iV9KyGru62+Y)~qSQ91}b~}w<&*}R&1c#$O`H@~c z5)2S_eXx}M#N{MuGeQS9@#UJB@;W_j50b}jIhxMPloEFQZdvwxiU^RYycTzgK)-vl3LT&$L8~@68$C8~5_U{cR$E#w*x65(qw&eoL@>%ZHvj zWnEMlSh*(o&oy|J7eJ5OD`ssy%F?*Vp?`Cq;FShyl{ZoKCG5g{y}>usznni#8ki(i zO{w@n{iAj1_ooX@+s*!uW60WcH~*bNOT6z%0jVML5};wVrQp~`Uss_{cO2oud_nNA8^B$?07fJ6?iI)Q zuo9G)O-z)DqstrBqf>B%S05hf-wep0@$BFHKSrkZ{za3D)yVzRz)2{wf8(Wp+xyAM z$rtyx$gi3A=V~V!`Q3;BM0$>*VVtxEM|xDL^gew7ydy3Q6YzD&THRz*q33Ms_D;M- zbCx1Ft#UNB)V3bf`~{ImI72OTp^|bF8?G8#FRj+Biy8ET5#rA3sd|0FR@U(LAJ%w8 zS1%n8Z=Amhw)92rIsof=YVWF4jw&F*j1LG@-`+cR0-~2LqXRH8(Ccne{y#MCPncF64U`0uO zWmi$dlii~1D0rLR{qc|_2M!C$t8^=G7xQY)9!#Y331A|>N)EhmyVdLWL9I3YLJ`7? zZmpqUJB>Ni9oiL)^1IK1UoMyhWE{$9M2M6Xi zPKk7GpMsA6vjZbU7~i+u|J6Nk|Ci!Y3UMUT2|`M;JsNQACdJ%ooo9Yt{?A+0hMpxi znEa~~sxC>rKrU6bd=WRb;%wsH>A#j4{({&1GYSNR57Gama(3)2A;SM>qop}l>Jk2* zn1+C$fIxuwzg3mCU#SOqb-wOCb6mBcYlA5+mt<&_J~sBxc(GQtBFINUO~Mr7<-uu($>P HJ4oML2Lo<@i8BwbL^1~GkG`E7C$SEa_ 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6f7a6eb3..66cd5a0e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf13..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30db..9d21a218 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/stopDaemon/TestPlugin.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/stopDaemon/TestPlugin.groovy index 143138f1..0c885d3c 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/stopDaemon/TestPlugin.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/stopDaemon/TestPlugin.groovy @@ -39,7 +39,7 @@ import org.gradle.internal.remote.internal.inet.InetAddressFactory import org.gradle.internal.service.ServiceRegistry import org.gradle.invocation.DefaultGradle import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.gradle.launcher.daemon.configuration.DaemonParameters +import org.gradle.launcher.daemon.configuration.DaemonPriority import org.gradle.launcher.daemon.context.DefaultDaemonContext import org.gradle.launcher.daemon.protocol.DaemonMessageSerializer import org.gradle.launcher.daemon.protocol.Finished @@ -52,7 +52,7 @@ import org.gradle.launcher.daemon.server.DaemonTcpServerConnector import org.gradle.launcher.daemon.server.DefaultDaemonConnection import org.gradle.launcher.daemon.server.IncomingConnectionHandler import org.gradle.launcher.daemon.server.SynchronizedDispatchConnection -import org.gradle.launcher.daemon.server.api.DaemonStateControl +import org.gradle.launcher.daemon.server.api.DaemonState import org.gradle.util.GradleVersion import net.fabricmc.loom.util.gradle.daemon.DaemonUtils @@ -78,7 +78,7 @@ class TestPlugin implements Plugin { // Write it in the registry def registry = new PersistentDaemonRegistry(registryBin.toFile(), services.get(FileLockManager.class), services.get(Chmod.class)) - def daemonInfo = new DaemonInfo(address, createDaemonContext(), "token".bytes, DaemonStateControl.State.Busy) + def daemonInfo = new DaemonInfo(address, createDaemonContext(), "token".bytes, DaemonState.Busy) registry.store(daemonInfo) // When we get a connection, wait for a stop message and process it by responding with a success message @@ -99,36 +99,19 @@ class TestPlugin implements Plugin { } } - // Thanks groovy for allowing me to do this :D static DefaultDaemonContext createDaemonContext() { - int constructorArgsCount = DefaultDaemonContext.class.getConstructors()[0].getParameterCount() - - if (constructorArgsCount == 10) { - // Gradle 8.9+ adds a JavaVersion and NativeServicesMode parameter to the constructor - //noinspection GroovyAssignabilityCheck - return new DefaultDaemonContext( - UUID.randomUUID().toString(), - new File("."), - JavaLanguageVersion.current(), - new File("."), - ProcessHandle.current().pid(), - 0, - List.of(), - false, - NativeServices.NativeServicesMode.NOT_SET, - DaemonParameters.Priority.NORMAL - ) - } - return new DefaultDaemonContext( UUID.randomUUID().toString(), new File("."), + JavaLanguageVersion.current(), + "", new File("."), ProcessHandle.current().pid(), 0, List.of(), false, - DaemonParameters.Priority.NORMAL + NativeServices.NativeServicesMode.NOT_SET, + DaemonPriority.NORMAL ) } From 3c3225900123390868a718f5553d552bd0385000 Mon Sep 17 00:00:00 2001 From: modmuss Date: Mon, 19 Aug 2024 15:18:13 +0100 Subject: [PATCH 4/4] New service system for source jar remapping (#1160) * New service system for source jar remapping * Remove the class generation, its too complex and useless. * Fix * Tests and cleanup * Add basic SourceRemapperService test * Skip file property tests on windows. --- .../loom/task/RemapSourcesJarTask.java | 30 ++-- .../loom/task/service/NewMappingsService.java | 140 +++++++++++++++ .../task/service/SourceRemapperService.java | 94 +++++----- .../loom/util/gradle/GradleTypeAdapter.java | 165 ++++++++++++++++++ .../loom/util/newService/ExampleService.java | 102 +++++++++++ .../util/newService/ScopedServiceFactory.java | 120 +++++++++++++ .../loom/util/newService/Service.java | 71 ++++++++ .../loom/util/newService/ServiceFactory.java | 54 ++++++ .../loom/util/newService/ServiceType.java | 52 ++++++ .../integration/ConfigurationCacheTest.groovy | 1 + .../test/unit/GradleTypeAdapterTest.groovy | 107 ++++++++++++ .../unit/service/MappingsServiceTest.groovy | 59 +++++++ .../service/ScopedServiceFactoryTest.groovy | 145 +++++++++++++++ .../test/unit/service/ServiceTestBase.groovy | 50 ++++++ .../service/SourceRemapperServiceTest.groovy | 95 ++++++++++ .../loom/test/util/GradleTestUtil.groovy | 7 + 16 files changed, 1226 insertions(+), 66 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/task/service/NewMappingsService.java create mode 100644 src/main/java/net/fabricmc/loom/util/gradle/GradleTypeAdapter.java create mode 100644 src/main/java/net/fabricmc/loom/util/newService/ExampleService.java create mode 100644 src/main/java/net/fabricmc/loom/util/newService/ScopedServiceFactory.java create mode 100644 src/main/java/net/fabricmc/loom/util/newService/Service.java create mode 100644 src/main/java/net/fabricmc/loom/util/newService/ServiceFactory.java create mode 100644 src/main/java/net/fabricmc/loom/util/newService/ServiceType.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/GradleTypeAdapterTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/service/MappingsServiceTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/service/ScopedServiceFactoryTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/service/ServiceTestBase.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/service/SourceRemapperServiceTest.groovy diff --git a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java index f0aa6d89..6bdecf65 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapSourcesJarTask.java @@ -33,34 +33,33 @@ import javax.inject.Inject; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.Property; -import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.fabricmc.loom.task.service.SourceRemapperService; -import net.fabricmc.loom.util.service.BuildSharedServiceManager; -import net.fabricmc.loom.util.service.UnsafeWorkQueueHelper; +import net.fabricmc.loom.util.newService.ScopedServiceFactory; public abstract class RemapSourcesJarTask extends AbstractRemapJarTask { - private final Provider serviceManagerProvider; + @Nested + abstract Property getSourcesRemapperServiceOptions(); @Inject public RemapSourcesJarTask() { super(); - serviceManagerProvider = BuildSharedServiceManager.createForTask(this, getBuildEventsListenerRegistry()); - getClasspath().from(getProject().getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)); getJarType().set("sources"); + + getSourcesRemapperServiceOptions().set(SourceRemapperService.createOptions(this)); } @TaskAction public void run() { submitWork(RemapSourcesAction.class, params -> { if (!params.namespacesMatch()) { - params.getSourcesRemapperServiceUuid().set(UnsafeWorkQueueHelper.create(SourceRemapperService.create(serviceManagerProvider.get().get(), this))); + params.getSourcesRemapperServiceOptions().set(getSourcesRemapperServiceOptions()); } }); } @@ -73,27 +72,24 @@ public abstract class RemapSourcesJarTask extends AbstractRemapJarTask { } public interface RemapSourcesParams extends AbstractRemapParams { - Property getSourcesRemapperServiceUuid(); + Property getSourcesRemapperServiceOptions(); } public abstract static class RemapSourcesAction extends AbstractRemapAction { private static final Logger LOGGER = LoggerFactory.getLogger(RemapSourcesAction.class); - private final @Nullable SourceRemapperService sourceRemapperService; - public RemapSourcesAction() { super(); - - sourceRemapperService = getParameters().getSourcesRemapperServiceUuid().isPresent() - ? UnsafeWorkQueueHelper.get(getParameters().getSourcesRemapperServiceUuid(), SourceRemapperService.class) - : null; } @Override public void execute() { try { - if (sourceRemapperService != null) { - sourceRemapperService.remapSourcesJar(inputFile, outputFile); + if (!getParameters().namespacesMatch()) { + try (var serviceFactory = new ScopedServiceFactory()) { + SourceRemapperService sourceRemapperService = serviceFactory.get(getParameters().getSourcesRemapperServiceOptions()); + sourceRemapperService.remapSourcesJar(inputFile, outputFile); + } } else { Files.copy(inputFile, outputFile, StandardCopyOption.REPLACE_EXISTING); } diff --git a/src/main/java/net/fabricmc/loom/task/service/NewMappingsService.java b/src/main/java/net/fabricmc/loom/task/service/NewMappingsService.java new file mode 100644 index 00000000..2e90f40e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/service/NewMappingsService.java @@ -0,0 +1,140 @@ +/* + * 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.service; + +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; + +import org.gradle.api.Project; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; +import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.loom.util.newService.Service; +import net.fabricmc.loom.util.newService.ServiceFactory; +import net.fabricmc.loom.util.newService.ServiceType; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.tree.MemoryMappingTree; +import net.fabricmc.tinyremapper.IMappingProvider; + +/** + * A service that provides mappings for remapping. + */ +public final class NewMappingsService extends Service implements Closeable { + public static ServiceType TYPE = new ServiceType<>(Options.class, NewMappingsService.class); + + public interface Options extends Service.Options { + @InputFile + RegularFileProperty getMappingsFile(); + @Input + Property getFrom(); + @Input + Property getTo(); + @Input + Property getRemapLocals(); + } + + /** + * Returns options for creating a new mappings service, with a given mappings file. + */ + public static Provider createOptions(Project project, Path mappingsFile, String from, String to, boolean remapLocals) { + return TYPE.create(project, o -> { + o.getMappingsFile().set(project.file(mappingsFile)); + o.getFrom().set(from); + o.getTo().set(to); + o.getRemapLocals().set(remapLocals); + }); + } + + /** + * Returns options for creating a new mappings service, using the mappings as specified in the project's mapping configuration. + */ + public static Provider createOptionsWithProjectMappings(Project project, String from, String to) { + final MappingConfiguration mappingConfiguration = LoomGradleExtension.get(project).getMappingConfiguration(); + return createOptions(project, mappingConfiguration.tinyMappings, from, to, false); + } + + public NewMappingsService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); + } + + private IMappingProvider mappingProvider = null; + private MemoryMappingTree memoryMappingTree = null; + + public IMappingProvider getMappingsProvider() { + if (mappingProvider == null) { + try { + mappingProvider = TinyRemapperHelper.create( + getMappingsPath(), + getFrom(), + getTo(), + getOptions().getRemapLocals().get() + ); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read mappings from: " + getMappingsPath(), e); + } + } + + return mappingProvider; + } + + public MemoryMappingTree getMemoryMappingTree() { + if (memoryMappingTree == null) { + memoryMappingTree = new MemoryMappingTree(); + + try { + MappingReader.read(getMappingsPath(), memoryMappingTree); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read mappings from: " + getMappingsPath(), e); + } + } + + return memoryMappingTree; + } + + public String getFrom() { + return getOptions().getFrom().get(); + } + + public String getTo() { + return getOptions().getTo().get(); + } + + public Path getMappingsPath() { + return getOptions().getMappingsFile().get().getAsFile().toPath(); + } + + @Override + public void close() { + mappingProvider = null; + } +} 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 b3f5456c..cf1b2c9b 100644 --- a/src/main/java/net/fabricmc/loom/task/service/SourceRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/SourceRemapperService.java @@ -26,55 +26,58 @@ 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.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Nested; 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.loom.util.newService.Service; +import net.fabricmc.loom.util.newService.ServiceFactory; +import net.fabricmc.loom.util.newService.ServiceType; import net.fabricmc.lorenztiny.TinyMappingsReader; -public final class SourceRemapperService implements SharedService { - public static synchronized SourceRemapperService create(SharedServiceManager serviceManager, 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 String id = extension.getMappingConfiguration().getBuildServiceName("sourceremapper", from, to); - final int javaCompileRelease = SourceRemapper.getJavaCompileRelease(project); +public final class SourceRemapperService extends Service { + public static ServiceType TYPE = new ServiceType<>(Options.class, SourceRemapperService.class); - return serviceManager.getOrCreateService(id, () -> - new SourceRemapperService(MappingsService.createDefault(project, serviceManager, from, to), task.getClasspath(), javaCompileRelease)); + public interface Options extends Service.Options { + @Nested + Property getMappings(); + @Input + Property getJavaCompileRelease(); + @InputFiles + ConfigurableFileCollection getClasspath(); + } + + public static Provider createOptions(RemapSourcesJarTask task) { + return TYPE.create(task.getProject(), o -> { + o.getMappings().set(NewMappingsService.createOptionsWithProjectMappings( + task.getProject(), + task.getSourceNamespace().get(), + task.getTargetNamespace().get() + )); + o.getJavaCompileRelease().set(SourceRemapper.getJavaCompileRelease(task.getProject())); + o.getClasspath().from(task.getClasspath()); + }); } private static final Logger LOGGER = LoggerFactory.getLogger(SourceRemapperService.class); - private final MappingsService mappingsService; - private final ConfigurableFileCollection classpath; - private final int javaCompileRelease; - - private final Supplier mercury = Suppliers.memoize(this::createMercury); - - private SourceRemapperService(MappingsService mappingsService, ConfigurableFileCollection classpath, int javaCompileRelease) { - this.mappingsService = mappingsService; - this.classpath = classpath; - this.javaCompileRelease = javaCompileRelease; + public SourceRemapperService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); } public void remapSourcesJar(Path source, Path destination) throws IOException { @@ -96,10 +99,17 @@ public final class SourceRemapperService implements SharedService { Files.delete(destination); } + Mercury mercury = createMercury(); + try (FileSystemUtil.Delegate dstFs = Files.isDirectory(destination) ? null : FileSystemUtil.getJarFileSystem(destination, true)) { Path dstPath = dstFs != null ? dstFs.get().getPath("/") : destination; - doRemap(srcPath, dstPath, source); + try { + mercury.rewrite(srcPath, dstPath); + } catch (Exception e) { + LOGGER.warn("Could not remap " + source + " fully!", e); + } + SourceRemapper.copyNonJavaFiles(srcPath, dstPath, LOGGER, source); } finally { if (isSrcTmp) { @@ -108,30 +118,16 @@ public final class SourceRemapperService implements SharedService { } } - private synchronized void doRemap(Path srcPath, Path dstPath, Path source) { - try { - 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(); - } - - private Mercury createMercury() { + private Mercury createMercury() throws IOException { var mercury = new Mercury(); mercury.setGracefulClasspathChecks(true); - mercury.setSourceCompatibilityFromRelease(javaCompileRelease); + mercury.setSourceCompatibilityFromRelease(getOptions().getJavaCompileRelease().get()); - try { - mercury.getProcessors().add(MercuryRemapper.create(getMappings())); - } catch (IOException e) { - throw new UncheckedIOException("Failed to read mercury mappings", e); - } + NewMappingsService mappingsService = getServiceFactory().get(getOptions().getMappings()); + var tinyMappingsReader = new TinyMappingsReader(mappingsService.getMemoryMappingTree(), mappingsService.getFrom(), mappingsService.getTo()).read(); + mercury.getProcessors().add(MercuryRemapper.create(tinyMappingsReader)); - for (File file : classpath.getFiles()) { + for (File file : getOptions().getClasspath().getFiles()) { if (file.exists()) { mercury.getClassPath().add(file.toPath()); } diff --git a/src/main/java/net/fabricmc/loom/util/gradle/GradleTypeAdapter.java b/src/main/java/net/fabricmc/loom/util/gradle/GradleTypeAdapter.java new file mode 100644 index 00000000..90e7173d --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/gradle/GradleTypeAdapter.java @@ -0,0 +1,165 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.gradle; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class GradleTypeAdapter implements TypeAdapterFactory { + public static final Gson GSON = new Gson().newBuilder() + .registerTypeAdapterFactory(new GradleTypeAdapter()) + .create(); + + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + final Class rawClass = type.getRawType(); + + if (FileCollection.class.isAssignableFrom(rawClass)) { + return new FileCollectionTypeAdapter(); + } else if (RegularFileProperty.class.isAssignableFrom(rawClass)) { + return new RegularFilePropertyTypeAdapter(); + } else if (ListProperty.class.isAssignableFrom(rawClass)) { + return new ListPropertyTypeAdapter(gson); + } else if (MapProperty.class.isAssignableFrom(rawClass)) { + return new MapPropertyTypeAdapter(gson); + } else if (Property.class.isAssignableFrom(rawClass)) { + return new PropertyTypeAdapter(gson); + } + + return null; + } + + private static final class PropertyTypeAdapter> extends WriteOnlyTypeAdapter { + private final Gson gson; + + private PropertyTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(JsonWriter out, T property) throws IOException { + final Object o = property.get(); + final TypeAdapter adapter = gson.getAdapter(o.getClass()); + adapter.write(out, o); + } + } + + private static final class FileCollectionTypeAdapter extends WriteOnlyTypeAdapter { + @Override + public void write(JsonWriter out, T fileCollection) throws IOException { + out.beginArray(); + + final List files = fileCollection.getFiles().stream() + .map(File::getAbsolutePath) + .sorted() + .toList(); + + for (String file : files) { + out.value(file); + } + + out.endArray(); + } + } + + private static final class RegularFilePropertyTypeAdapter extends WriteOnlyTypeAdapter { + @Override + public void write(JsonWriter out, T property) throws IOException { + final File file = property.get().getAsFile(); + out.value(file.getAbsolutePath()); + } + } + + private static final class ListPropertyTypeAdapter> extends WriteOnlyTypeAdapter { + private final Gson gson; + + private ListPropertyTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(JsonWriter out, T property) throws IOException { + List objects = property.get(); + out.beginArray(); + + for (Object o : objects) { + final TypeAdapter adapter = gson.getAdapter(o.getClass()); + adapter.write(out, o); + } + + out.endArray(); + } + } + + private static final class MapPropertyTypeAdapter> extends WriteOnlyTypeAdapter { + private final Gson gson; + + private MapPropertyTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(JsonWriter out, T property) throws IOException { + out.beginObject(); + + for (Map.Entry entry : property.get().entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + + if (!(key instanceof String)) { + throw new UnsupportedOperationException("Map keys must be strings"); + } + + out.name(entry.getKey().toString()); + final TypeAdapter adapter = gson.getAdapter(value.getClass()); + adapter.write(out, value); + } + + out.endObject(); + } + } + + private abstract static class WriteOnlyTypeAdapter extends TypeAdapter { + @Override + public final T read(JsonReader in) { + throw new UnsupportedOperationException("This type adapter is write-only"); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/newService/ExampleService.java b/src/main/java/net/fabricmc/loom/util/newService/ExampleService.java new file mode 100644 index 00000000..7417221c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/newService/ExampleService.java @@ -0,0 +1,102 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.newService; + +import java.io.Closeable; +import java.io.IOException; + +import org.gradle.api.Project; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Nested; + +// Note: This file mostly acts as documentation for the new service system. +// Services are classes that wrap expensive to create objects, such as mappings or tiny remapper. +// Services can be reused multiple times within a given scope, such as configuration or a single task action. +// Services need to have serializable inputs, used as a cache key and serialised to be passed between Gradle contexts. E.g task inputs, or work action params. +public final class ExampleService extends Service implements Closeable { + public static ServiceType TYPE = new ServiceType<>(Options.class, ExampleService.class); + + // Options use Gradle's Property's thus can be used in task inputs. + public interface Options extends Service.Options { + @Nested + Property getNested(); + } + + // Options can be created using data from the Project + static Provider createOptions(Project project) { + return TYPE.create(project, o -> { + o.getNested().set(AnotherService.createOptions(project, "example")); + }); + } + + // An example of how a service could be used, this could be within a task action. + // ServiceFactory would be similar to the existing ScopedSharedServiceManager + // Thus if a service with the same options has previously been created it will be reused. + static void howToUse(Options options, ServiceFactory factory) { + ExampleService exampleService = factory.get(options); + exampleService.doSomething(); + } + + public ExampleService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); + } + + public void doSomething() { + // The service factory used to the creation the current service can be used to get or create other services based on the current service's options. + AnotherService another = getServiceFactory().get(getOptions().getNested()); + System.out.println("ExampleService: " + another.getExample()); + } + + @Override + public void close() throws IOException { + // Anything that needs to be cleaned up when the service is no longer needed. + } + + public static final class AnotherService extends Service { + public static ServiceType TYPE = new ServiceType<>(Options.class, AnotherService.class); + + public interface Options extends Service.Options { + @Input + Property getExample(); + } + + static Provider createOptions(Project project, String example) { + return TYPE.create(project, o -> { + o.getExample().set(example); + }); + } + + public AnotherService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); + } + + // Services can expose any methods they wish, either to return data or do a job. + public String getExample() { + return getOptions().getExample().get(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/newService/ScopedServiceFactory.java b/src/main/java/net/fabricmc/loom/util/newService/ScopedServiceFactory.java new file mode 100644 index 00000000..1648a346 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/newService/ScopedServiceFactory.java @@ -0,0 +1,120 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.newService; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; + +import net.fabricmc.loom.util.gradle.GradleTypeAdapter; + +/** + * An implementation of {@link ServiceFactory} that creates services scoped to the factory instance. + * When the factory is closed, all services created by it are closed and discarded. + */ +public final class ScopedServiceFactory implements ServiceFactory, Closeable { + private final Map> servicesIdentityMap = new IdentityHashMap<>(); + private final Map> servicesJsonMap = new HashMap<>(); + + @Override + public > S get(O options) { + // First check if the service is already created, using the identity map saving the need to serialize the options + //noinspection unchecked + S service = (S) servicesIdentityMap.get(options); + + if (service != null) { + return service; + } + + // TODO skip serialization if we know there is no service with the same type + + // If the service is not already created, serialize the options and check the json map as it may be an equivalent service + String key = getOptionsCacheKey(options); + //noinspection unchecked + service = (S) servicesJsonMap.get(key); + + if (service != null) { + return service; + } + + service = createService(options, this); + + servicesIdentityMap.put(options, service); + servicesJsonMap.put(key, service); + + return service; + } + + private static > S createService(O options, ServiceFactory serviceFactory) { + // We need to create the service from the provided options + final Class serviceClass; + + // Find the service class + try { + //noinspection unchecked + serviceClass = (Class) Class.forName(options.getServiceClass().get()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Failed to find service class: " + options.getServiceClass().get(), e); + } + + try { + // Check there is only 1 constructor + if (serviceClass.getDeclaredConstructors().length != 1) { + throw new RuntimeException("Service class must have exactly 1 constructor"); + } + + // Check the constructor takes the correct types, the options class and a ScopedServiceFactory + Class[] parameterTypes = serviceClass.getDeclaredConstructors()[0].getParameterTypes(); + + if (parameterTypes.length != 2 || !parameterTypes[0].isAssignableFrom(options.getClass()) || !parameterTypes[1].isAssignableFrom(ServiceFactory.class)) { + throw new RuntimeException("Service class" + serviceClass.getName() + " constructor must take the options class and a ScopedServiceFactory"); + } + + //noinspection unchecked + return (S) serviceClass.getDeclaredConstructors()[0].newInstance(options, serviceFactory); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to create service instance", e); + } + } + + private String getOptionsCacheKey(Service.Options options) { + return GradleTypeAdapter.GSON.toJson(options); + } + + @Override + public void close() throws IOException { + for (Service service : servicesIdentityMap.values()) { + if (service instanceof Closeable closeable) { + closeable.close(); + } + } + + servicesIdentityMap.clear(); + servicesJsonMap.clear(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/newService/Service.java b/src/main/java/net/fabricmc/loom/util/newService/Service.java new file mode 100644 index 00000000..0e6ab54c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/newService/Service.java @@ -0,0 +1,71 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.newService; + +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.jetbrains.annotations.ApiStatus; + +/** + * A service is used to manage a set of data or a task that may be reused multiple times. + * + * @param The options type. + */ +public abstract class Service { + private final O options; + private final ServiceFactory serviceFactory; + + public Service(O options, ServiceFactory serviceFactory) { + this.options = options; + this.serviceFactory = serviceFactory; + } + + /** + * Gets the options for this service. + * + * @return The options. + */ + protected final O getOptions() { + return options; + } + + /** + * Return the factory that created this service, this can be used to get nested services. + * + * @return The {@link ServiceFactory} instance. + */ + protected ServiceFactory getServiceFactory() { + return serviceFactory; + } + + /** + * The base type of options class for a service. + */ + public interface Options { + @Input + @ApiStatus.Internal + Property getServiceClass(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/newService/ServiceFactory.java b/src/main/java/net/fabricmc/loom/util/newService/ServiceFactory.java new file mode 100644 index 00000000..e6fcdf6e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/newService/ServiceFactory.java @@ -0,0 +1,54 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.newService; + +import org.gradle.api.provider.Property; + +/** + * A factory for creating {@link Service} instances. + */ +public interface ServiceFactory { + /** + * Gets or creates a service instance with the given options. + * + * @param options The options to use. + * @param The options type. + * @param The service type. + * @return The service instance. + */ + default > S get(Property options) { + return get(options.get()); + } + + /** + * Gets or creates a service instance with the given options. + * + * @param options The options to use. + * @param The options type. + * @param The service type. + * @return The service instance. + */ + > S get(O options); +} diff --git a/src/main/java/net/fabricmc/loom/util/newService/ServiceType.java b/src/main/java/net/fabricmc/loom/util/newService/ServiceType.java new file mode 100644 index 00000000..13f47a18 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/newService/ServiceType.java @@ -0,0 +1,52 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.newService; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.provider.Provider; + +/** + * A record to hold the options and service class for a service. + * @param optionsClass The options class for the service. + * @param serviceClass The service class. + */ +public record ServiceType>(Class optionsClass, Class serviceClass) { + /** + * Create an instance of the options class for the given service class. + * @param project The {@link Project} to create the options for. + * @param action An action to configure the options. + * @return The created options instance. + */ + public Provider create(Project project, Action action) { + return project.provider(() -> { + O options = project.getObjects().newInstance(optionsClass); + options.getServiceClass().set(serviceClass.getName()); + options.getServiceClass().finalizeValue(); + action.execute(options); + return options; + }); + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/ConfigurationCacheTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/ConfigurationCacheTest.groovy index bcd9ea88..44d70bdd 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/ConfigurationCacheTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/ConfigurationCacheTest.groovy @@ -60,6 +60,7 @@ class ConfigurationCacheTest extends Specification implements GradleProjectTestT "configureClientLaunch" | _ "jar" | _ "check" | _ + "remapSourcesJar" | _ } // Test GradleUtils.configurationInputFile invalidates the cache when the file changes diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/GradleTypeAdapterTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/GradleTypeAdapterTest.groovy new file mode 100644 index 00000000..5efed261 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/GradleTypeAdapterTest.groovy @@ -0,0 +1,107 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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 org.gradle.api.file.FileCollection +import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import spock.lang.IgnoreIf +import spock.lang.Specification + +import net.fabricmc.loom.util.gradle.GradleTypeAdapter + +class GradleTypeAdapterTest extends Specification { + def "Property"() { + given: + def property = Mock(Property) + + when: + def json = GradleTypeAdapter.GSON.toJson(property) + + then: + 1 * property.get() >> "value" + json == "\"value\"" + } + + @IgnoreIf({ os.windows }) + def "FileCollection"() { + given: + def file1 = new File("file1") + def file2 = new File("file2") + def fileCollection = Mock(FileCollection) + + when: + def json = GradleTypeAdapter.GSON.toJson(fileCollection) + + then: + 1 * fileCollection.getFiles() >> [file1, file2].shuffled() + json == "[\"${file1.getAbsolutePath()}\",\"${file2.getAbsolutePath()}\"]" + } + + @IgnoreIf({ os.windows }) + def "RegularFileProperty"() { + given: + def file = new File("file") + def regularFileProperty = Mock(RegularFileProperty) + def regularFile = Mock(RegularFile) + + when: + def json = GradleTypeAdapter.GSON.toJson(regularFileProperty) + + then: + 1 * regularFileProperty.get() >> regularFile + 1 * regularFile.getAsFile() >> file + json == "\"${file.getAbsolutePath()}\"" + } + + def "ListProperty"() { + given: + def listProperty = Mock(ListProperty) + def list = ["value1", "value2"] + + when: + def json = GradleTypeAdapter.GSON.toJson(listProperty) + + then: + 1 * listProperty.get() >> list + json == "[\"value1\",\"value2\"]" + } + + def "MapProperty"() { + given: + def mapProperty = Mock(MapProperty) + def map = ["key1": "value1", "key2": "value2"] + + when: + def json = GradleTypeAdapter.GSON.toJson(mapProperty) + + then: + 1 * mapProperty.get() >> map + json == "{\"key1\":\"value1\",\"key2\":\"value2\"}" + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/service/MappingsServiceTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/service/MappingsServiceTest.groovy new file mode 100644 index 00000000..97232f09 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/service/MappingsServiceTest.groovy @@ -0,0 +1,59 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.service + +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property + +import net.fabricmc.loom.task.service.NewMappingsService +import net.fabricmc.loom.test.util.GradleTestUtil + +class MappingsServiceTest extends ServiceTestBase { + def "get mapping tree"() { + given: + NewMappingsService service = factory.get(new TestOptions( + mappingsFile: GradleTestUtil.mockRegularFileProperty(new File("src/test/resources/mappings/PosInChunk.mappings")), + from: GradleTestUtil.mockProperty("intermediary"), + to: GradleTestUtil.mockProperty("named"), + )) + + when: + def mappingTree = service.memoryMappingTree + + then: + mappingTree.getClasses().size() == 2 + + service.from == "intermediary" + service.to == "named" + } + + static class TestOptions implements NewMappingsService.Options { + RegularFileProperty mappingsFile + Property from + Property to + Property remapLocals = GradleTestUtil.mockProperty(false) + Property serviceClass = serviceClassProperty(NewMappingsService.TYPE) + } +} \ No newline at end of file diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/service/ScopedServiceFactoryTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/service/ScopedServiceFactoryTest.groovy new file mode 100644 index 00000000..0b8907e2 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/service/ScopedServiceFactoryTest.groovy @@ -0,0 +1,145 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.service + +import groovy.transform.InheritConstructors +import groovy.transform.TupleConstructor +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import spock.lang.Specification + +import net.fabricmc.loom.test.util.GradleTestUtil +import net.fabricmc.loom.util.newService.ScopedServiceFactory +import net.fabricmc.loom.util.newService.Service +import net.fabricmc.loom.util.newService.ServiceType + +class ScopedServiceFactoryTest extends Specification { + def "create service"() { + given: + def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello")) + def factory = new ScopedServiceFactory() + + when: + TestService service = factory.get(options) + + then: + service.getExample() == "hello" + + cleanup: + factory.close() + } + + def "reuse service"() { + given: + def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello")) + def factory = new ScopedServiceFactory() + + when: + TestService service = factory.get(options) + TestService service2 = factory.get(options) + + then: + service === service2 + + cleanup: + factory.close() + } + + def "reuse service different options instance"() { + given: + def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello")) + def options2 = new TestServiceOptions(GradleTestUtil.mockProperty("hello")) + def factory = new ScopedServiceFactory() + + when: + TestService service = factory.get(options) + TestService service2 = factory.get(options2) + + then: + service === service2 + + cleanup: + factory.close() + } + + def "Separate instances"() { + given: + def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello")) + def options2 = new TestServiceOptions(GradleTestUtil.mockProperty("world")) + def factory = new ScopedServiceFactory() + + when: + TestService service = factory.get(options) + TestService service2 = factory.get(options2) + + then: + service !== service2 + service.example == "hello" + service2.example == "world" + + cleanup: + factory.close() + } + + def "close service"() { + given: + def options = new TestServiceOptions(GradleTestUtil.mockProperty("hello")) + def factory = new ScopedServiceFactory() + + when: + TestService service = factory.get(options) + factory.close() + + then: + service.closed + } + + @InheritConstructors + static class TestService extends Service implements Closeable { + static ServiceType TYPE = new ServiceType(TestService.Options.class, TestService.class) + + interface Options extends Service.Options { + @Input + Property getExample(); + } + + boolean closed = false + + String getExample() { + return options.example.get() + } + + @Override + void close() throws Exception { + closed = true + } + } + + @TupleConstructor + static class TestServiceOptions implements TestService.Options { + Property example + Property serviceClass = ServiceTestBase.serviceClassProperty(TestService.TYPE) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/service/ServiceTestBase.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/service/ServiceTestBase.groovy new file mode 100644 index 00000000..7eba4b3f --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/service/ServiceTestBase.groovy @@ -0,0 +1,50 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.service + +import org.gradle.api.provider.Property +import spock.lang.Specification + +import net.fabricmc.loom.test.util.GradleTestUtil +import net.fabricmc.loom.util.newService.ScopedServiceFactory +import net.fabricmc.loom.util.newService.Service +import net.fabricmc.loom.util.newService.ServiceType + +abstract class ServiceTestBase extends Specification { + ScopedServiceFactory factory + + def setup() { + factory = new ScopedServiceFactory() + } + + def cleanup() { + factory.close() + factory = null + } + + static Property serviceClassProperty(ServiceType type) { + return GradleTestUtil.mockProperty(type.serviceClass().name) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/service/SourceRemapperServiceTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/service/SourceRemapperServiceTest.groovy new file mode 100644 index 00000000..51bfb230 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/service/SourceRemapperServiceTest.groovy @@ -0,0 +1,95 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2024 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.service + +import java.nio.file.Files +import java.nio.file.Path + +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.Property +import org.intellij.lang.annotations.Language + +import net.fabricmc.loom.task.service.NewMappingsService +import net.fabricmc.loom.task.service.SourceRemapperService +import net.fabricmc.loom.test.util.GradleTestUtil +import net.fabricmc.loom.util.DeletingFileVisitor + +class SourceRemapperServiceTest extends ServiceTestBase { + def "remap sources"() { + given: + Path tempDirectory = Files.createTempDirectory("test") + Path sourceDirectory = tempDirectory.resolve("source") + Path destDirectory = tempDirectory.resolve("dst") + Path mappings = tempDirectory.resolve("mappings.tiny") + + Files.createDirectories(sourceDirectory) + Files.createDirectories(destDirectory) + Files.writeString(sourceDirectory.resolve("Source.java"), SOURCE) + Files.writeString(mappings, MAPPINGS) + + SourceRemapperService service = factory.get(new TestOptions( + mappings: GradleTestUtil.mockProperty( + new MappingsServiceTest.TestOptions( + mappingsFile: GradleTestUtil.mockRegularFileProperty(mappings.toFile()), + from: GradleTestUtil.mockProperty("named"), + to: GradleTestUtil.mockProperty("intermediary"), + ) + ), + )) + + when: + service.remapSourcesJar(sourceDirectory, destDirectory) + + then: + // This isn't actually remapping, as we dont have the classpath setup at all. But that's not what we're testing here. + !Files.readString(destDirectory.resolve("Source.java")).isEmpty() + + cleanup: + Files.walkFileTree(tempDirectory, new DeletingFileVisitor()) + } + + @Language("java") + static String SOURCE = """ + class Source { + public void test() { + System.out.println("Hello"); + } + } + """.trim() + + // Tiny v2 mappings to rename println + static String MAPPINGS = """ + tiny 2 0 intermediary named + c Source Source + m ()V println test + """.trim() + + static class TestOptions implements SourceRemapperService.Options { + Property mappings + Property javaCompileRelease = GradleTestUtil.mockProperty(17) + ConfigurableFileCollection classpath = GradleTestUtil.mockConfigurableFileCollection() + Property serviceClass = serviceClassProperty(SourceRemapperService.TYPE) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy index 49448cf0..c98e8486 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy @@ -26,6 +26,7 @@ package net.fabricmc.loom.test.util import org.gradle.api.Project import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty import org.gradle.api.file.SourceDirectorySet @@ -132,6 +133,12 @@ class GradleTestUtil { return mock } + static ConfigurableFileCollection mockConfigurableFileCollection(File... files) { + def mock = mock(ConfigurableFileCollection.class) + when(mock.getFiles()).thenReturn(Set.of(files)) + return mock + } + static RepositoryHandler mockRepositoryHandler() { def mock = mock(RepositoryHandler.class) return mock