From d9349c57e1910c2b5bded6b1ab449d3fe9f4e489 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Mon, 23 Dec 2024 14:19:26 +0000 Subject: [PATCH 01/25] Bump version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 607f93b2..caf00733 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { } group = 'net.fabricmc' -def baseVersion = '1.9' +def baseVersion = '1.10' def ENV = System.getenv() if (ENV.BUILD_NUMBER) { From 40d17bacee1645b5647ff8f7499dcc625924066f Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Mon, 23 Dec 2024 12:56:40 +0000 Subject: [PATCH 02/25] Update to Gradle 8.12 --- .../bootstrap/LoomGradlePluginBootstrap.java | 2 +- build.gradle | 14 ++++---- gradle/libs.versions.toml | 2 +- gradle/test.libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 3 +- .../test/integration/FabricAPITest.groovy | 4 +-- src/test/resources/patches/fabric_api.patch | 32 ++++++++----------- .../projects/mavenLibrary/build.gradle | 4 +-- .../resources/projects/signed/build.gradle | 4 +-- 10 files changed, 31 insertions(+), 38 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 0b852973..35113243 100644 --- a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java +++ b/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java @@ -17,7 +17,7 @@ import org.gradle.util.GradleVersion; */ @SuppressWarnings("unused") public abstract class LoomGradlePluginBootstrap implements Plugin { - private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.11"; + private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.12"; private static final int MIN_SUPPORTED_MAJOR_JAVA_VERSION = 17; private static final int MIN_SUPPORTED_MAJOR_IDEA_VERSION = 2022; diff --git a/build.gradle b/build.gradle index caf00733..d63f0436 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ repositories { configurations { bootstrap { - transitive false + transitive = false } compileClasspath.extendsFrom bootstrap runtimeClasspath.extendsFrom bootstrap @@ -294,18 +294,18 @@ publishing { if (!ENV.EXPERIMENTAL) { // Also publish a snapshot so people can use the latest version if they wish snapshot(MavenPublication) { publication -> - groupId project.group - artifactId project.base.archivesName.get() - version baseVersion + '-SNAPSHOT' + groupId = project.group + artifactId = project.base.archivesName.get() + version = baseVersion + '-SNAPSHOT' from components.java } // Manually crate the plugin marker for snapshot versions snapshotPlugin(MavenPublication) { publication -> - groupId 'fabric-loom' - artifactId 'fabric-loom.gradle.plugin' - version baseVersion + '-SNAPSHOT' + groupId = 'fabric-loom' + artifactId = 'fabric-loom.gradle.plugin' + version = baseVersion + '-SNAPSHOT' pom.withXml({ // Based off org.gradle.plugin.devel.plugins.MavenPluginPublishPlugin diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 41500ffe..23f5f136 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.0.20" +kotlin = "2.0.21" asm = "9.7.1" commons-io = "2.15.1" gson = "2.10.1" diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index 4bc6312a..f098d010 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -6,7 +6,7 @@ mockito = "5.14.2" java-debug = "0.52.0" mixin = "0.15.3+mixin.0.8.7" -gradle-nightly = "8.12-20241110002642+0000" +gradle-nightly = "8.13-20241222002427+0000" fabric-loader = "0.16.9" fabric-installer = "1.0.1" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7cf748e7..e0fd0202 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.11-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d..f3b75f3b 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ 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 -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum 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 53547032..c1200bc3 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/FabricAPITest.groovy @@ -44,7 +44,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait { setup: def gradle = gradleProject( repo: "https://github.com/FabricMC/fabric.git", - commit: "70277babddfaf52ee30013af94764da19473b3b1", + commit: "d70d2c06bb8fafdb72c6778b29fb050618015ab3", version: version, patch: "fabric_api" ) @@ -58,7 +58,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait { """.stripIndent() } - def minecraftVersion = "1.21.4-pre3" + def minecraftVersion = "1.21.4" def server = ServerRunner.create(gradle.projectDir, minecraftVersion) .withMod(gradle.getOutputFile("fabric-api-999.0.0.jar")) diff --git a/src/test/resources/patches/fabric_api.patch b/src/test/resources/patches/fabric_api.patch index b2b77775..ae85767c 100644 --- a/src/test/resources/patches/fabric_api.patch +++ b/src/test/resources/patches/fabric_api.patch @@ -1,6 +1,6 @@ diff --git a/build.gradle b/build.gradle ---- a/build.gradle (revision 70277babddfaf52ee30013af94764da19473b3b1) -+++ b/build.gradle (date 1732875235843) +--- a/build.gradle (revision d70d2c06bb8fafdb72c6778b29fb050618015ab3) ++++ b/build.gradle (date 1734958436644) @@ -13,7 +13,7 @@ def ENV = System.getenv() @@ -36,23 +36,17 @@ diff --git a/build.gradle b/build.gradle } def getBranch() { -@@ -247,19 +230,6 @@ - - test { - useJUnitPlatform() -- -- afterEvaluate { -- // See: https://github.com/FabricMC/fabric-loader/pull/585 -- def classPathGroups = loom.mods.stream() -- .map { modSettings -> -- SourceSetHelper.getClasspath(modSettings, getProject()).stream() -- .map(File.&getAbsolutePath) -- .collect(Collectors.joining(File.pathSeparator)) -- } -- .collect(Collectors.joining(File.pathSeparator+File.pathSeparator)) -- -- systemProperty("fabric.classPathGroups", classPathGroups) -- } +@@ -250,10 +233,11 @@ } tasks.withType(ProcessResources).configureEach { +- inputs.property "version", project.version ++ def version = project.version ++ inputs.property "version", version + + filesMatching("fabric.mod.json") { +- expand "version": project.version ++ expand "version": version + } + } + diff --git a/src/test/resources/projects/mavenLibrary/build.gradle b/src/test/resources/projects/mavenLibrary/build.gradle index 2a31d1a3..5d641ff1 100644 --- a/src/test/resources/projects/mavenLibrary/build.gradle +++ b/src/test/resources/projects/mavenLibrary/build.gradle @@ -37,14 +37,14 @@ publishing { from components.java artifact(remapJar) { - classifier "classifier" + classifier = "classifier" } } } repositories { maven { - url "http://localhost:${System.getProperty("loom.test.mavenPort")}/" + url = "http://localhost:${System.getProperty("loom.test.mavenPort")}/" allowInsecureProtocol = true } } diff --git a/src/test/resources/projects/signed/build.gradle b/src/test/resources/projects/signed/build.gradle index 1d667cd8..8b2926f9 100644 --- a/src/test/resources/projects/signed/build.gradle +++ b/src/test/resources/projects/signed/build.gradle @@ -36,14 +36,14 @@ publishing { from components.java artifact(remapJar) { builtBy remapJar - classifier "classifier" + classifier = "classifier" } } } repositories { maven { - url "http://localhost:${System.getProperty("loom.test.mavenPort")}/" + url = "http://localhost:${System.getProperty("loom.test.mavenPort")}/" allowInsecureProtocol = true } } From 2ba633badf4f6019c3361192b0caa7a84588dc87 Mon Sep 17 00:00:00 2001 From: modmuss Date: Mon, 23 Dec 2024 14:24:39 +0000 Subject: [PATCH 03/25] Download source artifacts in parallel (#1232) --- .../mods/ModConfigurationRemapper.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java index 4de303cb..e5cfca02 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java @@ -35,6 +35,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Supplier; import com.google.common.collect.ImmutableMap; @@ -43,6 +44,8 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.FileCollectionDependency; import org.gradle.api.artifacts.MutableVersionConstraint; import org.gradle.api.artifacts.ResolvedArtifact; +import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; +import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.query.ArtifactResolutionQuery; import org.gradle.api.artifacts.result.ArtifactResult; @@ -243,7 +246,10 @@ public class ModConfigurationRemapper { private static List resolveArtifacts(Project project, Configuration configuration) { final List artifacts = new ArrayList<>(); - for (ResolvedArtifact artifact : configuration.getResolvedConfiguration().getResolvedArtifacts()) { + final Set resolvedArtifacts = configuration.getResolvedConfiguration().getResolvedArtifacts(); + downloadAllSources(project, resolvedArtifacts); + + for (ResolvedArtifact artifact : resolvedArtifacts) { final Path sources = findSources(project, artifact); artifacts.add(new ArtifactRef.ResolvedArtifactRef(artifact, sources)); } @@ -270,6 +276,27 @@ public class ModConfigurationRemapper { return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex); } + private static void downloadAllSources(Project project, Set resolvedArtifacts) { + if (isCIBuild()) { + return; + } + + final DependencyHandler dependencies = project.getDependencies(); + + List componentIdentifiers = resolvedArtifacts.stream() + .map(ResolvedArtifact::getId) + .map(ComponentArtifactIdentifier::getComponentIdentifier) + .toList(); + + //noinspection unchecked + ArtifactResolutionQuery query = dependencies.createArtifactResolutionQuery() + .forComponents(componentIdentifiers) + .withArtifacts(JvmLibrary.class, SourcesArtifact.class); + + // Run a single query for all of the artifacts, this will allow them to be resolved in parallel before they are queried individually + query.execute(); + } + @Nullable public static Path findSources(Project project, ResolvedArtifact artifact) { if (isCIBuild()) { From 3fce2627d67ace9684010e3b5e9f233f71e8a3e5 Mon Sep 17 00:00:00 2001 From: tranquillity-codes <120999089+tranquillity-codes@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:57:57 +0100 Subject: [PATCH 04/25] Make LineNumberRemapper stop trying to guess line numbers (#1235) Usually the guesses were wrong, so instead let's not generate a LineNumberTable for lines that lack a mapping in the linemap. This makes the behavior on decompiler bugs/issues more predictable. --- .../loom/decompilers/LineNumberRemapper.java | 14 ++++------- .../test/unit/LineNumberRemapperTests.groovy | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java b/src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java index 9b3ab78f..7eca471d 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java +++ b/src/main/java/net/fabricmc/loom/decompilers/LineNumberRemapper.java @@ -106,20 +106,16 @@ public record LineNumberRemapper(ClassLineNumbers lineNumbers) { return new MethodVisitor(api, super.visitMethod(access, name, descriptor, signature, exceptions)) { @Override public void visitLineNumber(int line, Label start) { - int tLine = line; - - if (tLine <= 0) { + if (line <= 0) { super.visitLineNumber(line, start); - } else if (tLine >= lineNumbers.maxLine()) { + } else if (line >= lineNumbers.maxLine()) { super.visitLineNumber(lineNumbers.maxLineDest(), start); } else { - Integer matchedLine = null; + Integer matchedLine = lineNumbers.lineMap().get(line); - while (tLine <= lineNumbers.maxLine() && ((matchedLine = lineNumbers.lineMap().get(tLine)) == null)) { - tLine++; + if (matchedLine != null) { + super.visitLineNumber(matchedLine, start); } - - super.visitLineNumber(matchedLine != null ? matchedLine : lineNumbers.maxLineDest(), start); } } }; diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/LineNumberRemapperTests.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/LineNumberRemapperTests.groovy index e07bfc70..c9ef451f 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/LineNumberRemapperTests.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/LineNumberRemapperTests.groovy @@ -65,6 +65,31 @@ class LineNumberRemapperTests extends Specification { readLineNumbers(unpacked) == [37, 39, 40] } + def "remapLinenumbersExclude"() { + given: + def className = LineNumberSource.class.name.replace('.', '/') + def input = ZipTestUtils.createZipFromBytes([(className + ".class"): getClassBytes(LineNumberSource.class)]) + + // + 10 to each line number + def entry = new ClassLineNumbers.Entry(className, 30, 40, [ + 27: 37, + 30: 40 + ]) + def lineNumbers = new ClassLineNumbers([(className): entry]) + + def outputJar = Files.createTempDirectory("loom").resolve("output.jar") + + when: + def remapper = new LineNumberRemapper(lineNumbers) + remapper.process(input, outputJar) + + def unpacked = ZipUtils.unpack(outputJar, className + ".class") + + then: + readLineNumbers(getClassBytes(LineNumberSource.class)) == [27, 29, 30] + readLineNumbers(unpacked) == [37, 40] + } + static byte[] getClassBytes(Class clazz) { return clazz.classLoader.getResourceAsStream(clazz.name.replace('.', '/') + ".class").withCloseable { it.bytes From cc51c64c37fe40c3df9e0ecbf1b2ea56344fd025 Mon Sep 17 00:00:00 2001 From: modmuss Date: Sun, 29 Dec 2024 15:56:17 +0000 Subject: [PATCH 05/25] Fix #1229 (#1237) --- .../configuration/CompileConfiguration.java | 4 +++ .../MojangMappingsProjectTest.groovy | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 93c34b56..c4489640 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -166,9 +166,13 @@ public abstract class CompileConfiguration implements Runnable { extension.setMinecraftProvider(minecraftProvider); minecraftProvider.provide(); + // Realise the dependencies without actually resolving them, this forces any lazy providers to be created, populating the layered mapping factories. + project.getConfigurations().getByName(Configurations.MAPPINGS).getDependencies().toArray(); + // Created any layered mapping files. LayeredMappingsFactory.afterEvaluate(configContext); + // Resolve the mapping files from the configuration final DependencyInfo mappingsDep = DependencyInfo.create(getProject(), Configurations.MAPPINGS); final MappingConfiguration mappingConfiguration = MappingConfiguration.create(getProject(), configContext.serviceFactory(), mappingsDep, minecraftProvider); extension.setMappingConfiguration(mappingConfiguration); diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/MojangMappingsProjectTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/MojangMappingsProjectTest.groovy index 086778ad..4669f362 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/MojangMappingsProjectTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/MojangMappingsProjectTest.groovy @@ -120,4 +120,30 @@ class MojangMappingsProjectTest extends Specification implements GradleProjectTe where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "mojang mappings via lazy provider (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + + gradle.buildGradle << ''' + dependencies { + minecraft "com.mojang:minecraft:1.18-pre5" + mappings project.provider { + loom.layered() { + officialMojangMappings() + } + } + } + ''' + + when: + def result = gradle.run(task: "build") + + then: + result.task(":build").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } } From 44ca4b8b8adc47d195e66c597a1b0c092566df97 Mon Sep 17 00:00:00 2001 From: Space Walker <48224626+SpaceWalkerRS@users.noreply.github.com> Date: Sun, 29 Dec 2024 16:57:11 +0100 Subject: [PATCH 06/25] Run library processor for server-only projects for Minecraft versions without bundle metadata (#1231) * run library processor for server-only projects without bundle metadata * implement suggested changes --- .../providers/minecraft/MinecraftLibraryProvider.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java index de9a80fd..1f6c8404 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java @@ -109,12 +109,7 @@ public class MinecraftLibraryProvider { private void provideServerLibraries() { final BundleMetadata serverBundleMetadata = minecraftProvider.getServerBundleMetadata(); - - if (serverBundleMetadata == null) { - return; - } - - final List libraries = MinecraftLibraryHelper.getServerLibraries(serverBundleMetadata); + final List libraries = serverBundleMetadata != null ? MinecraftLibraryHelper.getServerLibraries(serverBundleMetadata) : Collections.emptyList(); final List processLibraries = processLibraries(libraries); processLibraries.forEach(this::applyServerLibrary); } From 543d0a3d107edbead60d27aec9b0e4a740e64465 Mon Sep 17 00:00:00 2001 From: Space Walker <48224626+SpaceWalkerRS@users.noreply.github.com> Date: Sun, 29 Dec 2024 16:57:45 +0100 Subject: [PATCH 07/25] create backup jars for legacy merged mapped minecraft providers (#1230) * create backup jars for legacy merged mapped mc providers * only merge jars if remapping occurred * Cleanup + add logger * Fixes --------- Co-authored-by: modmuss50 --- .../AbstractMappedMinecraftProvider.java | 71 +++++++++++++------ .../mapped/IntermediaryMinecraftProvider.java | 33 +++++++-- .../mapped/NamedMinecraftProvider.java | 32 +++++++-- .../ProcessedNamedMinecraftProvider.java | 10 ++- 4 files changed, 111 insertions(+), 35 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java index e55c9928..bf3e0791 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/AbstractMappedMinecraftProvider.java @@ -37,6 +37,8 @@ import java.util.StringJoiner; import java.util.function.Function; import org.gradle.api.Project; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; @@ -56,6 +58,8 @@ import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.TinyRemapper; public abstract class AbstractMappedMinecraftProvider implements MappedMinecraftProvider.ProviderImpl { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMappedMinecraftProvider.class); + protected final M minecraftProvider; private final Project project; protected final LoomGradleExtension extension; @@ -68,8 +72,18 @@ public abstract class AbstractMappedMinecraftProvider getRemappedJars(); + /** + * @return A list of output jars that this provider generates + */ + public List getOutputJars() { + return getRemappedJars(); + } + // Returns a list of MinecraftJar.Type's that this provider exports to be used as a dependency public List getDependencyTypes() { return Collections.emptyList(); @@ -85,7 +99,7 @@ public abstract class AbstractMappedMinecraftProvider minecraftJars) { - for (MinecraftJar minecraftJar : minecraftJars) { - if (!Files.exists(getBackupJarPath(minecraftJar))) { - return false; - } - } - - return true; - } - protected void createBackupJars(List minecraftJars) throws IOException { for (MinecraftJar minecraftJar : minecraftJars) { Files.copy(minecraftJar.getPath(), getBackupJarPath(minecraftJar), StandardCopyOption.REPLACE_EXISTING); @@ -193,14 +197,34 @@ public abstract class AbstractMappedMinecraftProvider remappedJars) { - for (RemappedJars remappedJar : remappedJars) { - if (!getMavenHelper(remappedJar.type()).exists(null)) { - return false; + protected boolean shouldRefreshOutputs(ProvideContext context) { + if (context.refreshOutputs()) { + LOGGER.info("Refreshing outputs for mapped jar, as refresh outputs was requested"); + return true; + } + + final List outputJars = getOutputJars(); + + if (outputJars.isEmpty()) { + throw new IllegalStateException("No output jars provided"); + } + + for (OutputJar outputJar : outputJars) { + if (!getMavenHelper(outputJar.type()).exists(null)) { + LOGGER.info("Refreshing outputs for mapped jar, as {} does not exist", outputJar.outputJar()); + return true; } } - return true; + for (OutputJar outputJar : outputJars) { + if (!Files.exists(getBackupJarPath(outputJar.outputJar()))) { + LOGGER.info("Refreshing outputs for mapped jar, as backup jar does not exist for {}", outputJar.outputJar()); + return true; + } + } + + LOGGER.debug("All outputs are up to date"); + return false; } private void remapInputs(List remappedJars, ConfigContext configContext) throws IOException { @@ -274,7 +298,15 @@ public abstract class AbstractMappedMinecraftProvider provide(ProvideContext context) throws Exception { + final List minecraftJars = List.of(getMergedJar()); + + // this check must be done before the client and server impls are provided + // because the merging only needs to happen if the remapping step is run + final boolean refreshOutputs = client.shouldRefreshOutputs(context) + || server.shouldRefreshOutputs(context) + || this.shouldRefreshOutputs(context); + // Map the client and server jars separately server.provide(context); client.provide(context); - // then merge them - MergedMinecraftProvider.mergeJars( - client.getEnvOnlyJar().toFile(), - server.getEnvOnlyJar().toFile(), - getMergedJar().toFile() - ); + if (refreshOutputs) { + // then merge them + MergedMinecraftProvider.mergeJars( + client.getEnvOnlyJar().toFile(), + server.getEnvOnlyJar().toFile(), + getMergedJar().toFile() + ); - return List.of(getMergedJar()); + createBackupJars(minecraftJars); + } + + return minecraftJars; } @Override @@ -98,6 +110,13 @@ public abstract sealed class IntermediaryMinecraftProvider getOutputJars() { + return List.of( + new SimpleOutputJar(getMergedJar()) + ); + } + @Override public List getDependencyTypes() { return List.of(MinecraftJar.Type.MERGED); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java index f2753609..02077a98 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/NamedMinecraftProvider.java @@ -85,17 +85,28 @@ public abstract class NamedMinecraftProvider extend @Override public List provide(ProvideContext context) throws Exception { final ProvideContext childContext = context.withApplyDependencies(false); + final List minecraftJars = List.of(getMergedJar()); + + // this check must be done before the client and server impls are provided + // because the merging only needs to happen if the remapping step is run + final boolean refreshOutputs = client.shouldRefreshOutputs(childContext) + || server.shouldRefreshOutputs(childContext) + || this.shouldRefreshOutputs(childContext); // Map the client and server jars separately server.provide(childContext); client.provide(childContext); - // then merge them - MergedMinecraftProvider.mergeJars( - client.getEnvOnlyJar().toFile(), - server.getEnvOnlyJar().toFile(), - getMergedJar().toFile() - ); + if (refreshOutputs) { + // then merge them + MergedMinecraftProvider.mergeJars( + client.getEnvOnlyJar().toFile(), + server.getEnvOnlyJar().toFile(), + getMergedJar().toFile() + ); + + createBackupJars(minecraftJars); + } getMavenHelper(MinecraftJar.Type.MERGED).savePom(); @@ -106,7 +117,7 @@ public abstract class NamedMinecraftProvider extend ); } - return List.of(getMergedJar()); + return minecraftJars; } @Override @@ -115,6 +126,13 @@ public abstract class NamedMinecraftProvider extend throw new UnsupportedOperationException("LegacyMergedImpl does not support getRemappedJars"); } + @Override + public List getOutputJars() { + return List.of( + new SimpleOutputJar(getMergedJar()) + ); + } + @Override public List getDependencyTypes() { return List.of(MinecraftJar.Type.MERGED); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java index 9a5f28c1..1987f9e7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/mapped/ProcessedNamedMinecraftProvider.java @@ -65,7 +65,7 @@ public abstract class ProcessedNamedMinecraftProvider getOutputJars() { + return parentMinecraftProvider.getMinecraftJars().stream() + .map(this::getProcessedJar) + .map(SimpleOutputJar::new) + .toList(); + } + @Override public MavenScope getMavenScope() { return MavenScope.LOCAL; From 454e32ece76f5c4c376ece644793f954d3e19582 Mon Sep 17 00:00:00 2001 From: modmuss Date: Wed, 1 Jan 2025 14:25:54 +0000 Subject: [PATCH 08/25] Refactor Fabric API extension (#1238) * Refactor Fabric API extension * Fix * Fix * Even more cleanup --- .../net/fabricmc/loom/LoomGradlePlugin.java | 5 +- .../api/fabricapi/DataGenerationSettings.java | 70 ++++ .../api/fabricapi/FabricApiExtension.java | 61 ++++ .../configuration/FabricApiExtension.java | 344 ------------------ .../fabricapi/FabricApiAbstractSourceSet.java | 110 ++++++ .../fabricapi/FabricApiDataGeneration.java | 145 ++++++++ .../fabricapi/FabricApiExtensionImpl.java | 67 ++++ .../fabricapi/FabricApiVersions.java | 157 ++++++++ .../test/unit/FabricApiExtensionTest.groovy | 6 +- 9 files changed, 616 insertions(+), 349 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/api/fabricapi/DataGenerationSettings.java create mode 100644 src/main/java/net/fabricmc/loom/api/fabricapi/FabricApiExtension.java delete mode 100644 src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiDataGeneration.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiExtensionImpl.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiVersions.java diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index 89cdbf7a..144c548f 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -34,9 +34,10 @@ import org.gradle.api.Project; import org.gradle.api.plugins.PluginAware; import net.fabricmc.loom.api.LoomGradleExtensionAPI; +import net.fabricmc.loom.api.fabricapi.FabricApiExtension; import net.fabricmc.loom.bootstrap.BootstrappedPlugin; import net.fabricmc.loom.configuration.CompileConfiguration; -import net.fabricmc.loom.configuration.FabricApiExtension; +import net.fabricmc.loom.configuration.fabricapi.FabricApiExtensionImpl; import net.fabricmc.loom.configuration.LoomConfigurations; import net.fabricmc.loom.configuration.MavenPublication; import net.fabricmc.loom.configuration.ide.idea.IdeaConfiguration; @@ -85,7 +86,7 @@ public class LoomGradlePlugin implements BootstrappedPlugin { // Setup extensions project.getExtensions().create(LoomGradleExtensionAPI.class, "loom", LoomGradleExtensionImpl.class, project, LoomFiles.create(project)); - project.getExtensions().create("fabricApi", FabricApiExtension.class); + project.getExtensions().create(FabricApiExtension.class, "fabricApi", FabricApiExtensionImpl.class); for (Class jobClass : SETUP_JOBS) { project.getObjects().newInstance(jobClass).run(); diff --git a/src/main/java/net/fabricmc/loom/api/fabricapi/DataGenerationSettings.java b/src/main/java/net/fabricmc/loom/api/fabricapi/DataGenerationSettings.java new file mode 100644 index 00000000..4b86632c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/fabricapi/DataGenerationSettings.java @@ -0,0 +1,70 @@ +/* + * 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.api.fabricapi; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; + +/** + * Represents the settings for data generation. + */ +public interface DataGenerationSettings { + /** + * Contains the output directory where generated data files will be stored. + */ + RegularFileProperty getOutputDirectory(); + + /** + * Contains a boolean indicating whether a run configuration should be created for the data generation process. + */ + Property getCreateRunConfiguration(); + + /** + * Contains a boolean property indicating whether a new source set should be created for the data generation process. + */ + Property getCreateSourceSet(); + + /** + * Contains a string property representing the mod ID associated with the data generation process. + * + *

This must be set when {@link #getCreateRunConfiguration()} is set. + */ + Property getModId(); + + /** + * Contains a boolean property indicating whether strict validation is enabled. + */ + Property getStrictValidation(); + + /** + * Contains a boolean property indicating whether the generated resources will be automatically added to the main sourceset. + */ + Property getAddToResources(); + + /** + * Contains a boolean property indicating whether data generation will be compiled and ran with the client. + */ + Property getClient(); +} diff --git a/src/main/java/net/fabricmc/loom/api/fabricapi/FabricApiExtension.java b/src/main/java/net/fabricmc/loom/api/fabricapi/FabricApiExtension.java new file mode 100644 index 00000000..e05f7062 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/fabricapi/FabricApiExtension.java @@ -0,0 +1,61 @@ +/* + * 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.api.fabricapi; + +import org.gradle.api.Action; +import org.gradle.api.artifacts.Dependency; + +/** + * A gradle extension with specific functionality related to Fabric API. + */ +public interface FabricApiExtension { + /** + * Get a {@link Dependency} for a given Fabric API module. + * + * @param moduleName The name of the module. + * @param fabricApiVersion The main Fabric API version. + * @return A {@link Dependency} for the module. + */ + Dependency module(String moduleName, String fabricApiVersion); + + /** + * Get the version of a Fabric API module. + * @param moduleName The name of the module. + * @param fabricApiVersion The main Fabric API version. + * @return The version of the module. + */ + String moduleVersion(String moduleName, String fabricApiVersion); + + /** + * Configuration data generation using the default settings. + */ + void configureDataGeneration(); + + /** + * Configuration data generation using the specified settings. + * @param action An action to configure specific data generation settings. See {@link DataGenerationSettings} for more information. + */ + void configureDataGeneration(Action action); +} diff --git a/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java b/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java deleted file mode 100644 index 26d6b271..00000000 --- a/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2020-2023 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package net.fabricmc.loom.configuration; - -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.inject.Inject; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.gradle.api.Action; -import org.gradle.api.Project; -import org.gradle.api.artifacts.ConfigurationContainer; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.TaskContainer; -import org.gradle.jvm.tasks.Jar; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; -import net.fabricmc.loom.util.download.DownloadException; -import net.fabricmc.loom.util.fmj.FabricModJson; -import net.fabricmc.loom.util.fmj.FabricModJsonFactory; -import net.fabricmc.loom.util.gradle.SourceSetHelper; - -public abstract class FabricApiExtension { - @Inject - public abstract Project getProject(); - - private static final String DATAGEN_SOURCESET_NAME = "datagen"; - - private static final HashMap> moduleVersionCache = new HashMap<>(); - private static final HashMap> deprecatedModuleVersionCache = new HashMap<>(); - - public Dependency module(String moduleName, String fabricApiVersion) { - return getProject().getDependencies() - .create(getDependencyNotation(moduleName, fabricApiVersion)); - } - - public String moduleVersion(String moduleName, String fabricApiVersion) { - String moduleVersion = moduleVersionCache - .computeIfAbsent(fabricApiVersion, this::getApiModuleVersions) - .get(moduleName); - - if (moduleVersion == null) { - moduleVersion = deprecatedModuleVersionCache - .computeIfAbsent(fabricApiVersion, this::getDeprecatedApiModuleVersions) - .get(moduleName); - } - - if (moduleVersion == null) { - throw new RuntimeException("Failed to find module version for module: " + moduleName); - } - - return moduleVersion; - } - - /** - * Configure data generation with the default options. - */ - public void configureDataGeneration() { - configureDataGeneration(dataGenerationSettings -> { }); - } - - /** - * Configure data generation with custom options. - */ - public void configureDataGeneration(Action action) { - final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); - final TaskContainer taskContainer = getProject().getTasks(); - - DataGenerationSettings settings = getProject().getObjects().newInstance(DataGenerationSettings.class); - settings.getOutputDirectory().set(getProject().file("src/main/generated")); - settings.getCreateRunConfiguration().convention(true); - settings.getCreateSourceSet().convention(false); - settings.getStrictValidation().convention(false); - settings.getAddToResources().convention(true); - settings.getClient().convention(false); - - action.execute(settings); - - final SourceSet mainSourceSet = SourceSetHelper.getMainSourceSet(getProject()); - final File outputDirectory = settings.getOutputDirectory().getAsFile().get(); - - if (settings.getAddToResources().get()) { - mainSourceSet.resources(files -> { - // Add the src/main/generated to the main sourceset's resources. - Set srcDirs = new HashSet<>(files.getSrcDirs()); - srcDirs.add(outputDirectory); - files.setSrcDirs(srcDirs); - }); - } - - // Exclude the cache dir from the output jar to ensure reproducibility. - taskContainer.getByName(JavaPlugin.JAR_TASK_NAME, task -> { - Jar jar = (Jar) task; - jar.exclude(".cache/**"); - }); - - if (settings.getCreateSourceSet().get()) { - final boolean isClientAndSplit = extension.areEnvironmentSourceSetsSplit() && settings.getClient().get(); - - SourceSetContainer sourceSets = SourceSetHelper.getSourceSets(getProject()); - - // Create the new datagen sourceset, depend on the main or client sourceset. - SourceSet dataGenSourceSet = sourceSets.create(DATAGEN_SOURCESET_NAME, sourceSet -> { - dependsOn(sourceSet, mainSourceSet); - - if (isClientAndSplit) { - dependsOn(sourceSet, SourceSetHelper.getSourceSetByName(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME, getProject())); - } - }); - - settings.getModId().convention(getProject().provider(() -> { - try { - final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), dataGenSourceSet); - - if (fabricModJson == null) { - throw new RuntimeException("Could not find a fabric.mod.json file in the data source set or a value for DataGenerationSettings.getModId()"); - } - - return fabricModJson.getId(); - } catch (IOException e) { - throw new org.gradle.api.UncheckedIOException("Failed to read mod id from the datagen source set.", e); - } - })); - - extension.getMods().create(settings.getModId().get(), mod -> { - // Create a classpath group for this mod. Assume that the main sourceset is already in a group. - mod.sourceSet(DATAGEN_SOURCESET_NAME); - }); - - extension.createRemapConfigurations(sourceSets.getByName(DATAGEN_SOURCESET_NAME)); - } - - if (settings.getCreateRunConfiguration().get()) { - extension.getRunConfigs().create("datagen", run -> { - run.inherit(extension.getRunConfigs().getByName(settings.getClient().get() ? "client" : "server")); - run.setConfigName("Data Generation"); - - run.property("fabric-api.datagen"); - run.property("fabric-api.datagen.output-dir", outputDirectory.getAbsolutePath()); - run.runDir("build/datagen"); - - if (settings.getModId().isPresent()) { - run.property("fabric-api.datagen.modid", settings.getModId().get()); - } - - if (settings.getStrictValidation().get()) { - run.property("fabric-api.datagen.strict-validation", "true"); - } - - if (settings.getCreateSourceSet().get()) { - run.source(DATAGEN_SOURCESET_NAME); - } - }); - - // Add the output directory as an output allowing the task to be skipped. - getProject().getTasks().named("runDatagen", task -> { - task.getOutputs().dir(outputDirectory); - }); - } - } - - public interface DataGenerationSettings { - /** - * Contains the output directory where generated data files will be stored. - */ - RegularFileProperty getOutputDirectory(); - - /** - * Contains a boolean indicating whether a run configuration should be created for the data generation process. - */ - Property getCreateRunConfiguration(); - - /** - * Contains a boolean property indicating whether a new source set should be created for the data generation process. - */ - Property getCreateSourceSet(); - - /** - * Contains a string property representing the mod ID associated with the data generation process. - * - *

This must be set when {@link #getCreateRunConfiguration()} is set. - */ - Property getModId(); - - /** - * Contains a boolean property indicating whether strict validation is enabled. - */ - Property getStrictValidation(); - - /** - * Contains a boolean property indicating whether the generated resources will be automatically added to the main sourceset. - */ - Property getAddToResources(); - - /** - * Contains a boolean property indicating whether data generation will be compiled and ran with the client. - */ - Property getClient(); - } - - private String getDependencyNotation(String moduleName, String fabricApiVersion) { - return String.format("net.fabricmc.fabric-api:%s:%s", moduleName, moduleVersion(moduleName, fabricApiVersion)); - } - - private Map getApiModuleVersions(String fabricApiVersion) { - try { - return populateModuleVersionMap(getApiMavenPom(fabricApiVersion)); - } catch (PomNotFoundException e) { - throw new RuntimeException("Could not find fabric-api version: " + fabricApiVersion); - } - } - - private Map getDeprecatedApiModuleVersions(String fabricApiVersion) { - try { - return populateModuleVersionMap(getDeprecatedApiMavenPom(fabricApiVersion)); - } catch (PomNotFoundException e) { - // Not all fabric-api versions have deprecated modules, return an empty map to cache this fact. - return Collections.emptyMap(); - } - } - - private Map populateModuleVersionMap(File pomFile) { - try { - DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); - Document pom = docBuilder.parse(pomFile); - - Map versionMap = new HashMap<>(); - - NodeList dependencies = ((Element) pom.getElementsByTagName("dependencies").item(0)).getElementsByTagName("dependency"); - - for (int i = 0; i < dependencies.getLength(); i++) { - Element dep = (Element) dependencies.item(i); - Element artifact = (Element) dep.getElementsByTagName("artifactId").item(0); - Element version = (Element) dep.getElementsByTagName("version").item(0); - - if (artifact == null || version == null) { - throw new RuntimeException("Failed to find artifact or version"); - } - - versionMap.put(artifact.getTextContent(), version.getTextContent()); - } - - return versionMap; - } catch (Exception e) { - throw new RuntimeException("Failed to parse " + pomFile.getName(), e); - } - } - - private File getApiMavenPom(String fabricApiVersion) throws PomNotFoundException { - return getPom("fabric-api", fabricApiVersion); - } - - private File getDeprecatedApiMavenPom(String fabricApiVersion) throws PomNotFoundException { - return getPom("fabric-api-deprecated", fabricApiVersion); - } - - private File getPom(String name, String version) throws PomNotFoundException { - final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); - final var mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/%s-%s.pom".formatted(name, version)); - - try { - extension.download(String.format("https://maven.fabricmc.net/net/fabricmc/fabric-api/%2$s/%1$s/%2$s-%1$s.pom", version, name)) - .defaultCache() - .downloadPath(mavenPom.toPath()); - } catch (DownloadException e) { - if (e.getStatusCode() == 404) { - throw new PomNotFoundException(e); - } - - throw new UncheckedIOException("Failed to download maven info to " + mavenPom.getName(), e); - } - - return mavenPom; - } - - private static class PomNotFoundException extends Exception { - PomNotFoundException(Throwable cause) { - super(cause); - } - } - - private static void extendsFrom(Project project, String name, String extendsFrom) { - final ConfigurationContainer configurations = project.getConfigurations(); - - configurations.named(name, configuration -> { - configuration.extendsFrom(configurations.getByName(extendsFrom)); - }); - } - - private void dependsOn(SourceSet sourceSet, SourceSet other) { - sourceSet.setCompileClasspath( - sourceSet.getCompileClasspath() - .plus(other.getOutput()) - ); - - sourceSet.setRuntimeClasspath( - sourceSet.getRuntimeClasspath() - .plus(other.getOutput()) - ); - - extendsFrom(getProject(), sourceSet.getCompileClasspathConfigurationName(), other.getCompileClasspathConfigurationName()); - extendsFrom(getProject(), sourceSet.getRuntimeClasspathConfigurationName(), other.getRuntimeClasspathConfigurationName()); - } -} diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java new file mode 100644 index 00000000..6ffdce0c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java @@ -0,0 +1,110 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.fabricapi; + +import java.io.IOException; + +import javax.inject.Inject; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; +import net.fabricmc.loom.util.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; +import net.fabricmc.loom.util.gradle.SourceSetHelper; + +abstract class FabricApiAbstractSourceSet { + @Inject + protected abstract Project getProject(); + + protected abstract String getSourceSetName(); + + protected void configureSourceSet(Property modId, boolean isClient) { + final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + final SourceSet mainSourceSet = SourceSetHelper.getMainSourceSet(getProject()); + + final boolean isClientAndSplit = extension.areEnvironmentSourceSetsSplit() && isClient; + + SourceSetContainer sourceSets = SourceSetHelper.getSourceSets(getProject()); + + // Create the new datagen sourceset, depend on the main or client sourceset. + SourceSet dataGenSourceSet = sourceSets.create(getSourceSetName(), sourceSet -> { + dependsOn(sourceSet, mainSourceSet); + + if (isClientAndSplit) { + dependsOn(sourceSet, SourceSetHelper.getSourceSetByName(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME, getProject())); + } + }); + + modId.convention(getProject().provider(() -> { + try { + final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), dataGenSourceSet); + + if (fabricModJson == null) { + throw new RuntimeException("Could not find a fabric.mod.json file in the data source set or a value for DataGenerationSettings.getModId()"); + } + + return fabricModJson.getId(); + } catch (IOException e) { + throw new org.gradle.api.UncheckedIOException("Failed to read mod id from the datagen source set.", e); + } + })); + + extension.getMods().create(modId.get(), mod -> { + // Create a classpath group for this mod. Assume that the main sourceset is already in a group. + mod.sourceSet(getSourceSetName()); + }); + + extension.createRemapConfigurations(sourceSets.getByName(getSourceSetName())); + } + + private static void extendsFrom(Project project, String name, String extendsFrom) { + final ConfigurationContainer configurations = project.getConfigurations(); + + configurations.named(name, configuration -> { + configuration.extendsFrom(configurations.getByName(extendsFrom)); + }); + } + + private void dependsOn(SourceSet sourceSet, SourceSet other) { + sourceSet.setCompileClasspath( + sourceSet.getCompileClasspath() + .plus(other.getOutput()) + ); + + sourceSet.setRuntimeClasspath( + sourceSet.getRuntimeClasspath() + .plus(other.getOutput()) + ); + + extendsFrom(getProject(), sourceSet.getCompileClasspathConfigurationName(), other.getCompileClasspathConfigurationName()); + extendsFrom(getProject(), sourceSet.getRuntimeClasspathConfigurationName(), other.getRuntimeClasspathConfigurationName()); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiDataGeneration.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiDataGeneration.java new file mode 100644 index 00000000..836df856 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiDataGeneration.java @@ -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.configuration.fabricapi; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +import javax.inject.Inject; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.jvm.tasks.Jar; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.fabricapi.DataGenerationSettings; +import net.fabricmc.loom.util.gradle.SourceSetHelper; + +public abstract class FabricApiDataGeneration extends FabricApiAbstractSourceSet { + @Inject + protected abstract Project getProject(); + + @Inject + public FabricApiDataGeneration() { + } + + @Override + protected String getSourceSetName() { + return "datagen"; + } + + void configureDataGeneration(Action action) { + final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + final TaskContainer taskContainer = getProject().getTasks(); + + DataGenerationSettings settings = getProject().getObjects().newInstance(DataGenerationSettings.class); + settings.getOutputDirectory().set(getProject().file("src/main/generated")); + settings.getCreateRunConfiguration().convention(true); + settings.getCreateSourceSet().convention(false); + settings.getStrictValidation().convention(false); + settings.getAddToResources().convention(true); + settings.getClient().convention(false); + + action.execute(settings); + + final SourceSet mainSourceSet = SourceSetHelper.getMainSourceSet(getProject()); + final File outputDirectory = settings.getOutputDirectory().getAsFile().get(); + + if (settings.getAddToResources().get()) { + mainSourceSet.resources(files -> { + // Add the src/main/generated to the main sourceset's resources. + Set srcDirs = new HashSet<>(files.getSrcDirs()); + srcDirs.add(outputDirectory); + files.setSrcDirs(srcDirs); + }); + } + + // Exclude the cache dir from the output jar to ensure reproducibility. + taskContainer.getByName(JavaPlugin.JAR_TASK_NAME, task -> { + Jar jar = (Jar) task; + jar.exclude(".cache/**"); + }); + + if (settings.getCreateSourceSet().get()) { + configureSourceSet(settings.getModId(), settings.getClient().get()); + } + + if (settings.getCreateRunConfiguration().get()) { + extension.getRunConfigs().create("datagen", run -> { + run.inherit(extension.getRunConfigs().getByName(settings.getClient().get() ? "client" : "server")); + run.setConfigName("Data Generation"); + + run.property("fabric-api.datagen"); + run.property("fabric-api.datagen.output-dir", outputDirectory.getAbsolutePath()); + run.runDir("build/datagen"); + + if (settings.getModId().isPresent()) { + run.property("fabric-api.datagen.modid", settings.getModId().get()); + } + + if (settings.getStrictValidation().get()) { + run.property("fabric-api.datagen.strict-validation", "true"); + } + + if (settings.getCreateSourceSet().get()) { + run.source(getSourceSetName()); + } + }); + + // Add the output directory as an output allowing the task to be skipped. + getProject().getTasks().named("runDatagen", task -> { + task.getOutputs().dir(outputDirectory); + }); + } + } + + private static void extendsFrom(Project project, String name, String extendsFrom) { + final ConfigurationContainer configurations = project.getConfigurations(); + + configurations.named(name, configuration -> { + configuration.extendsFrom(configurations.getByName(extendsFrom)); + }); + } + + private void dependsOn(SourceSet sourceSet, SourceSet other) { + sourceSet.setCompileClasspath( + sourceSet.getCompileClasspath() + .plus(other.getOutput()) + ); + + sourceSet.setRuntimeClasspath( + sourceSet.getRuntimeClasspath() + .plus(other.getOutput()) + ); + + extendsFrom(getProject(), sourceSet.getCompileClasspathConfigurationName(), other.getCompileClasspathConfigurationName()); + extendsFrom(getProject(), sourceSet.getRuntimeClasspathConfigurationName(), other.getRuntimeClasspathConfigurationName()); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiExtensionImpl.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiExtensionImpl.java new file mode 100644 index 00000000..c19a1d8f --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiExtensionImpl.java @@ -0,0 +1,67 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2023 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.fabricapi; + +import javax.inject.Inject; + +import org.gradle.api.Action; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.model.ObjectFactory; + +import net.fabricmc.loom.api.fabricapi.DataGenerationSettings; +import net.fabricmc.loom.api.fabricapi.FabricApiExtension; + +public abstract class FabricApiExtensionImpl implements FabricApiExtension { + @Inject + protected abstract ObjectFactory getObjectFactory(); + + private final FabricApiVersions versions; + private final FabricApiDataGeneration dataGeneration; + + public FabricApiExtensionImpl() { + versions = getObjectFactory().newInstance(FabricApiVersions.class); + dataGeneration = getObjectFactory().newInstance(FabricApiDataGeneration.class); + } + + @Override + public Dependency module(String moduleName, String fabricApiVersion) { + return versions.module(moduleName, fabricApiVersion); + } + + @Override + public String moduleVersion(String moduleName, String fabricApiVersion) { + return versions.moduleVersion(moduleName, fabricApiVersion); + } + + @Override + public void configureDataGeneration() { + configureDataGeneration(dataGenerationSettings -> { }); + } + + @Override + public void configureDataGeneration(Action action) { + dataGeneration.configureDataGeneration(action); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiVersions.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiVersions.java new file mode 100644 index 00000000..937a0733 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiVersions.java @@ -0,0 +1,157 @@ +/* + * 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.configuration.fabricapi; + +import java.io.File; +import java.io.UncheckedIOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.util.download.DownloadException; + +public abstract class FabricApiVersions { + @Inject + protected abstract Project getProject(); + + private final HashMap> moduleVersionCache = new HashMap<>(); + private final HashMap> deprecatedModuleVersionCache = new HashMap<>(); + + public Dependency module(String moduleName, String fabricApiVersion) { + return getProject().getDependencies() + .create(getDependencyNotation(moduleName, fabricApiVersion)); + } + + public String moduleVersion(String moduleName, String fabricApiVersion) { + String moduleVersion = moduleVersionCache + .computeIfAbsent(fabricApiVersion, this::getApiModuleVersions) + .get(moduleName); + + if (moduleVersion == null) { + moduleVersion = deprecatedModuleVersionCache + .computeIfAbsent(fabricApiVersion, this::getDeprecatedApiModuleVersions) + .get(moduleName); + } + + if (moduleVersion == null) { + throw new RuntimeException("Failed to find module version for module: " + moduleName); + } + + return moduleVersion; + } + + private String getDependencyNotation(String moduleName, String fabricApiVersion) { + return String.format("net.fabricmc.fabric-api:%s:%s", moduleName, moduleVersion(moduleName, fabricApiVersion)); + } + + private Map getApiModuleVersions(String fabricApiVersion) { + try { + return populateModuleVersionMap(getApiMavenPom(fabricApiVersion)); + } catch (PomNotFoundException e) { + throw new RuntimeException("Could not find fabric-api version: " + fabricApiVersion); + } + } + + private Map getDeprecatedApiModuleVersions(String fabricApiVersion) { + try { + return populateModuleVersionMap(getDeprecatedApiMavenPom(fabricApiVersion)); + } catch (PomNotFoundException e) { + // Not all fabric-api versions have deprecated modules, return an empty map to cache this fact. + return Collections.emptyMap(); + } + } + + private Map populateModuleVersionMap(File pomFile) { + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document pom = docBuilder.parse(pomFile); + + Map versionMap = new HashMap<>(); + + NodeList dependencies = ((Element) pom.getElementsByTagName("dependencies").item(0)).getElementsByTagName("dependency"); + + for (int i = 0; i < dependencies.getLength(); i++) { + Element dep = (Element) dependencies.item(i); + Element artifact = (Element) dep.getElementsByTagName("artifactId").item(0); + Element version = (Element) dep.getElementsByTagName("version").item(0); + + if (artifact == null || version == null) { + throw new RuntimeException("Failed to find artifact or version"); + } + + versionMap.put(artifact.getTextContent(), version.getTextContent()); + } + + return versionMap; + } catch (Exception e) { + throw new RuntimeException("Failed to parse " + pomFile.getName(), e); + } + } + + private File getApiMavenPom(String fabricApiVersion) throws PomNotFoundException { + return getPom("fabric-api", fabricApiVersion); + } + + private File getDeprecatedApiMavenPom(String fabricApiVersion) throws PomNotFoundException { + return getPom("fabric-api-deprecated", fabricApiVersion); + } + + private File getPom(String name, String version) throws PomNotFoundException { + final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + final var mavenPom = new File(extension.getFiles().getUserCache(), "fabric-api/%s-%s.pom".formatted(name, version)); + + try { + extension.download(String.format("https://maven.fabricmc.net/net/fabricmc/fabric-api/%2$s/%1$s/%2$s-%1$s.pom", version, name)) + .defaultCache() + .downloadPath(mavenPom.toPath()); + } catch (DownloadException e) { + if (e.getStatusCode() == 404) { + throw new PomNotFoundException(e); + } + + throw new UncheckedIOException("Failed to download maven info to " + mavenPom.getName(), e); + } + + return mavenPom; + } + + private static class PomNotFoundException extends Exception { + PomNotFoundException(Throwable cause) { + super(cause); + } + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/FabricApiExtensionTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/FabricApiExtensionTest.groovy index 860e65eb..0e4b5403 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/FabricApiExtensionTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/FabricApiExtensionTest.groovy @@ -27,13 +27,13 @@ package net.fabricmc.loom.test.unit import org.gradle.api.Project import spock.lang.Specification -import net.fabricmc.loom.configuration.FabricApiExtension +import net.fabricmc.loom.configuration.fabricapi.FabricApiVersions import net.fabricmc.loom.test.util.GradleTestUtil class FabricApiExtensionTest extends Specification { def "get module version"() { when: - def fabricApi = new FabricApiExtension() { + def fabricApi = new FabricApiVersions() { Project project = GradleTestUtil.mockProject() } def version = fabricApi.moduleVersion(moduleName, apiVersion) @@ -51,7 +51,7 @@ class FabricApiExtensionTest extends Specification { def "unknown module"() { when: - def fabricApi = new FabricApiExtension() { + def fabricApi = new FabricApiVersions() { Project project = GradleTestUtil.mockProject() } fabricApi.moduleVersion("fabric-api-unknown", apiVersion) From c46e252643a128ecb955f1ec6a0d50b10a9c8176 Mon Sep 17 00:00:00 2001 From: modmuss Date: Thu, 2 Jan 2025 14:25:20 +0000 Subject: [PATCH 09/25] Add DSL to configure Fabric API game tests (#1240) * Add DSL to configure game tests * Small cleanup * More work --- .../api/fabricapi/FabricApiExtension.java | 16 +- .../loom/api/fabricapi/GameTestSettings.java | 82 ++++++++++ .../fabricapi/FabricApiExtensionImpl.java | 15 +- .../fabricapi/FabricApiTesting.java | 144 ++++++++++++++++++ .../net/fabricmc/loom/task/LoomTasks.java | 2 +- .../integration/DataGenerationTest.groovy | 60 +++++++- 6 files changed, 311 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java diff --git a/src/main/java/net/fabricmc/loom/api/fabricapi/FabricApiExtension.java b/src/main/java/net/fabricmc/loom/api/fabricapi/FabricApiExtension.java index e05f7062..c308f59e 100644 --- a/src/main/java/net/fabricmc/loom/api/fabricapi/FabricApiExtension.java +++ b/src/main/java/net/fabricmc/loom/api/fabricapi/FabricApiExtension.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2024 FabricMC + * Copyright (c) 2024-2025 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 @@ -26,6 +26,7 @@ package net.fabricmc.loom.api.fabricapi; import org.gradle.api.Action; import org.gradle.api.artifacts.Dependency; +import org.jetbrains.annotations.ApiStatus; /** * A gradle extension with specific functionality related to Fabric API. @@ -58,4 +59,17 @@ public interface FabricApiExtension { * @param action An action to configure specific data generation settings. See {@link DataGenerationSettings} for more information. */ void configureDataGeneration(Action action); + + /** + * Configuration of game and client tests using the default settings. + */ + @ApiStatus.Experimental + void configureTests(); + + /** + * Configuration of game and/or client tests using the specified settings. + * @param action An action to configure specific game test settings. See {@link GameTestSettings} for more information. + */ + @ApiStatus.Experimental + void configureTests(Action action); } diff --git a/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java b/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java new file mode 100644 index 00000000..5675484a --- /dev/null +++ b/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java @@ -0,0 +1,82 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.api.fabricapi; + +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Optional; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents the settings for game and/or client tests. + */ +@ApiStatus.Experimental +public interface GameTestSettings { + /** + * Contains a boolean property indicating whether a new source set should be created for the tests. + * + *

Default: false + */ + Property getCreateSourceSet(); + + /** + * Contains a string property representing the mod ID associated with the tests. + * + *

This must be set when {@link #getCreateSourceSet()} is set. + */ + @Optional + Property getModId(); + + /** + * Contains a boolean property indicating whether a run configuration will be created for the server side game tests, using Vanilla Game Test framework. + * + *

Default: true + */ + Property getEnableGameTests(); + + /** + * Contains a boolean property indicating whether a run configuration will be created for the client side game tests, using the Fabric API Client Test framework. + * + *

Default: true + */ + Property getEnableClientGameTests(); + + /** + * Contains a boolean property indicating whether the eula has been accepted. By enabling this you agree to the Minecraft EULA located at https://aka.ms/MinecraftEULA. + * + *

This only works when {@link #getEnableClientGameTests()} is enabled. + * + *

Default: false + */ + Property getEula(); + + /** + * Contains a boolean property indicating whether the run directories should be cleared before running the tests. + * + *

This only works when {@link #getEnableClientGameTests()} is enabled. + * + *

Default: true + */ + Property getClearRunDirectory(); +} diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiExtensionImpl.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiExtensionImpl.java index c19a1d8f..0e7de09e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiExtensionImpl.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2020-2023 FabricMC + * Copyright (c) 2020-2025 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 @@ -32,6 +32,7 @@ import org.gradle.api.model.ObjectFactory; import net.fabricmc.loom.api.fabricapi.DataGenerationSettings; import net.fabricmc.loom.api.fabricapi.FabricApiExtension; +import net.fabricmc.loom.api.fabricapi.GameTestSettings; public abstract class FabricApiExtensionImpl implements FabricApiExtension { @Inject @@ -39,10 +40,12 @@ public abstract class FabricApiExtensionImpl implements FabricApiExtension { private final FabricApiVersions versions; private final FabricApiDataGeneration dataGeneration; + private final FabricApiTesting testing; public FabricApiExtensionImpl() { versions = getObjectFactory().newInstance(FabricApiVersions.class); dataGeneration = getObjectFactory().newInstance(FabricApiDataGeneration.class); + testing = getObjectFactory().newInstance(FabricApiTesting.class); } @Override @@ -64,4 +67,14 @@ public abstract class FabricApiExtensionImpl implements FabricApiExtension { public void configureDataGeneration(Action action) { dataGeneration.configureDataGeneration(action); } + + @Override + public void configureTests() { + configureTests(gameTestSettings -> { }); + } + + @Override + public void configureTests(Action action) { + testing.configureTests(action); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java new file mode 100644 index 00000000..c3585db3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java @@ -0,0 +1,144 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.fabricapi; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Consumer; + +import javax.inject.Inject; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.Delete; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskContainer; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.fabricapi.GameTestSettings; +import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.task.AbstractLoomTask; +import net.fabricmc.loom.task.LoomTasks; +import net.fabricmc.loom.util.Constants; + +public abstract class FabricApiTesting extends FabricApiAbstractSourceSet { + @Inject + protected abstract Project getProject(); + + @Inject + public FabricApiTesting() { + } + + @Override + protected String getSourceSetName() { + return "gametest"; + } + + void configureTests(Action action) { + final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + final TaskContainer tasks = getProject().getTasks(); + + GameTestSettings settings = getProject().getObjects().newInstance(GameTestSettings.class); + settings.getCreateSourceSet().convention(false); + settings.getEnableGameTests().convention(true); + settings.getEnableClientGameTests().convention(true); + settings.getEula().convention(false); + settings.getClearRunDirectory().convention(true); + + action.execute(settings); + + if (settings.getCreateSourceSet().get()) { + configureSourceSet(settings.getModId(), true); + } + + Consumer configureBase = run -> { + if (settings.getCreateSourceSet().get()) { + run.source(getSourceSetName()); + } + }; + + if (settings.getEnableGameTests().get()) { + RunConfigSettings gameTest = extension.getRunConfigs().create("gameTest", run -> { + run.inherit(extension.getRunConfigs().getByName("server")); + run.property("fabric-api.gametest"); + run.runDir("build/run/gameTest"); + configureBase.accept(run); + }); + + tasks.named("test", task -> task.dependsOn(LoomTasks.getRunConfigTaskName(gameTest))); + } + + if (settings.getEnableClientGameTests().get()) { + RunConfigSettings clientGameTest = extension.getRunConfigs().create("clientGameTest", run -> { + run.inherit(extension.getRunConfigs().getByName("client")); + run.property("fabric.client.gametest"); + run.runDir("build/run/clientGameTest"); + configureBase.accept(run); + }); + + if (settings.getClearRunDirectory().get()) { + var deleteGameTestRunDir = tasks.register("deleteGameTestRunDir", Delete.class, task -> { + task.setGroup(Constants.TaskGroup.FABRIC); + task.delete(clientGameTest.getRunDir()); + }); + + tasks.named(LoomTasks.getRunConfigTaskName(clientGameTest), task -> task.dependsOn(deleteGameTestRunDir)); + } + + if (settings.getEula().get()) { + var acceptEula = tasks.register("acceptGameTestEula", AcceptEulaTask.class, task -> { + task.getEulaFile().set(getProject().file(clientGameTest.getRunDir() + "/eula.txt")); + + if (settings.getClearRunDirectory().get()) { + // Ensure that the eula is accepted after the run directory is cleared + task.dependsOn(tasks.named("deleteGameTestRunDir")); + } + }); + + tasks.named("configureLaunch", task -> task.dependsOn(acceptEula)); + } + } + } + + public abstract static class AcceptEulaTask extends AbstractLoomTask { + @OutputFile + public abstract RegularFileProperty getEulaFile(); + + @TaskAction + public void acceptEula() throws IOException { + final Path eula = getEulaFile().get().getAsFile().toPath(); + + if (Files.notExists(eula)) { + Files.writeString(eula, """ + #This file was generated by the Fabric Loom Gradle plugin. As the user opted into accepting the EULA. + eula=true + """); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 44d1b5f6..27ddf591 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -126,7 +126,7 @@ public abstract class LoomTasks implements Runnable { }); } - private static String getRunConfigTaskName(RunConfigSettings config) { + public static String getRunConfigTaskName(RunConfigSettings config) { String configName = config.getName(); return "run" + configName.substring(0, 1).toUpperCase() + configName.substring(1); } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DataGenerationTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DataGenerationTest.groovy index 94e9d293..9f80f5ad 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/DataGenerationTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DataGenerationTest.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2023 FabricMC + * Copyright (c) 2023-2025 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 @@ -24,6 +24,7 @@ package net.fabricmc.loom.test.integration +import spock.lang.IgnoreIf import spock.lang.Specification import spock.lang.Unroll @@ -31,15 +32,16 @@ import net.fabricmc.loom.test.util.GradleProjectTestTrait import static net.fabricmc.loom.test.LoomTestConstants.PRE_RELEASE_GRADLE import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS +import static org.gradle.testkit.runner.TaskOutcome.FAILED import static org.gradle.testkit.runner.TaskOutcome.SUCCESS class DataGenerationTest extends Specification implements GradleProjectTestTrait { private static String DEPENDENCIES = """ dependencies { - minecraft "com.mojang:minecraft:1.20.2" - mappings "net.fabricmc:yarn:1.20.2+build.4:v2" - modImplementation "net.fabricmc:fabric-loader:0.14.23" - modImplementation "net.fabricmc.fabric-api:fabric-api:0.90.0+1.20.2" + minecraft "com.mojang:minecraft:1.21.4" + mappings "net.fabricmc:yarn:1.21.4+build.4:v2" + modImplementation "net.fabricmc:fabric-loader:0.16.9" + modImplementation "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4" } """ @@ -202,4 +204,52 @@ class DataGenerationTest extends Specification implements GradleProjectTestTrait then: result.task(":runDatagen").outcome == SUCCESS } + + @Unroll + def "game tests (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildGradle << ''' + fabricApi { + configureTests() + } + ''' + DEPENDENCIES + when: + def result = gradle.run(task: "runGameTest", expectFailure: true) + + then: + // We expect this to fail because there is nothing to test + // At least we know that Fabric API is attempting to run the tests + result.task(":runGameTest").outcome == FAILED + result.output.contains("No test functions were given!") + + where: + version << STANDARD_TEST_VERSIONS + } + + @Unroll + @IgnoreIf({ System.getenv("CI") != null }) // This test is disabled on CI because it launches a real client and cannot run headless. + def "client game tests (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildGradle << ''' + fabricApi { + configureTests { + createSourceSet = true + modId = "example-test" + eula = true + } + } + ''' + DEPENDENCIES + when: + def result = gradle.run(task: "runClientGameTest") + def eula = new File(gradle.projectDir, "build/run/clientGameTest/eula.txt") + + then: + result.task(":runClientGameTest").outcome == SUCCESS + eula.text.contains("eula=true") + + where: + version << STANDARD_TEST_VERSIONS + } } From 8b6658c55966614a040b17e64f0581bdce7fbd7b Mon Sep 17 00:00:00 2001 From: modmuss Date: Thu, 2 Jan 2025 14:25:32 +0000 Subject: [PATCH 10/25] Production run tasks (#1241) * Production run tasks * Fix * Add some docs --- gradle/runtime.libs.versions.toml | 4 +- gradle/test.libs.versions.toml | 4 +- .../configuration/LoomConfigurations.java | 5 + .../task/prod/AbstractProductionRunTask.java | 203 ++++++++++++++++++ .../task/prod/ClientProductionRunTask.java | 93 ++++++++ .../task/prod/ServerProductionRunTask.java | 108 ++++++++++ .../net/fabricmc/loom/util/Constants.java | 4 + .../test/integration/RunConfigTest.groovy | 52 ++++- .../loom/test/util/ServerRunner.groovy | 3 +- 9 files changed, 470 insertions(+), 6 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java create mode 100644 src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java create mode 100644 src/main/java/net/fabricmc/loom/task/prod/ServerProductionRunTask.java diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml index e35da821..0e3f7cf1 100644 --- a/gradle/runtime.libs.versions.toml +++ b/gradle/runtime.libs.versions.toml @@ -10,6 +10,7 @@ dev-launch-injector = "0.2.1+build.8" terminal-console-appender = "1.3.0" jetbrains-annotations = "25.0.0" native-support = "1.0.1" +fabric-installer = "1.0.1" [libraries] # Decompilers @@ -22,4 +23,5 @@ mixin-compile-extensions = { module = "net.fabricmc:fabric-mixin-compile-extensi dev-launch-injector = { module = "net.fabricmc:dev-launch-injector", version.ref = "dev-launch-injector" } terminal-console-appender = { module = "net.minecrell:terminalconsoleappender", version.ref = "terminal-console-appender" } jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } -native-support = { module = "net.fabricmc:fabric-loom-native-support", version.ref = "native-support" } \ No newline at end of file +native-support = { module = "net.fabricmc:fabric-loom-native-support", version.ref = "native-support" } +fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" } \ No newline at end of file diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index f098d010..357c1c28 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -8,7 +8,6 @@ mixin = "0.15.3+mixin.0.8.7" gradle-nightly = "8.13-20241222002427+0000" fabric-loader = "0.16.9" -fabric-installer = "1.0.1" [libraries] spock = { module = "org.spockframework:spock-core", version.ref = "spock" } @@ -19,5 +18,4 @@ mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } java-debug = { module = "com.microsoft.java:com.microsoft.java.debug.core", version.ref = "java-debug" } mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "mixin" } gradle-nightly = { module = "org.gradle:dummy", version.ref = "gradle-nightly" } -fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } -fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" } \ No newline at end of file +fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } \ No newline at end of file diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java index 9d265443..8922f3af 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java @@ -153,6 +153,11 @@ public abstract class LoomConfigurations implements Runnable { getDependencies().add(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, LoomVersions.TERMINAL_CONSOLE_APPENDER.mavenNotation()); getDependencies().add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation()); getDependencies().add(JavaPlugin.TEST_COMPILE_ONLY_CONFIGURATION_NAME, LoomVersions.JETBRAINS_ANNOTATIONS.mavenNotation()); + + register(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Role.RESOLVABLE); + extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.MINECRAFT_NATIVES); + extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.MINECRAFT_CLIENT_RUNTIME_LIBRARIES); + extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.LOADER_DEPENDENCIES); } private NamedDomainObjectProvider register(String name, Role role) { diff --git a/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java b/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java new file mode 100644 index 00000000..15c45439 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java @@ -0,0 +1,203 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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.prod; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; +import org.gradle.jvm.toolchain.JavaLauncher; +import org.gradle.jvm.toolchain.JavaToolchainService; +import org.gradle.jvm.toolchain.JavaToolchainSpec; +import org.gradle.process.ExecOperations; +import org.gradle.process.ExecResult; +import org.gradle.process.ExecSpec; +import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.configuration.InstallerData; +import net.fabricmc.loom.task.AbstractLoomTask; +import net.fabricmc.loom.task.RemapTaskConfiguration; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.gradle.GradleUtils; + +/** + * This is the base task for running the game in a "production" like environment. Using intermediary names, and not enabling development only features. + * + *

Do not use this task directly, use {@link ClientProductionRunTask} or {@link ServerProductionRunTask} instead. + */ +@ApiStatus.Experimental +public abstract sealed class AbstractProductionRunTask extends AbstractLoomTask permits ClientProductionRunTask, ServerProductionRunTask { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractProductionRunTask.class); + + /** + * A collection of mods that will be used when running the game. The mods must be remapped to run with intermediary names. + * + *

By default this includes the remapped jar. + */ + @Classpath + public abstract ConfigurableFileCollection getMods(); + + /** + * A list of additional JVM arguments to pass to the game. + */ + @Input + public abstract ListProperty getJvmArgs(); + + /** + * A list of additional program arguments to pass to the game. + */ + @Input + public abstract ListProperty getProgramArgs(); + + /** + * The directory to run the game in. + */ + @OutputDirectory + public abstract DirectoryProperty getRunDir(); + + /** + * The {@link JavaLauncher} to use when running the game, this can be used to specify a specific Java version to use. + * + *

See: Java Toolchains + * @return + */ + @Nested + public abstract Property getJavaLauncher(); + + // Internal options + @ApiStatus.Internal + @Classpath + protected abstract ConfigurableFileCollection getClasspath(); + + @ApiStatus.Internal + @Input + protected abstract Property getMainClass(); + + @Inject + protected abstract ExecOperations getExecOperations(); + + @Inject + protected abstract JavaToolchainService getJavaToolchainService(); + + @Inject + public AbstractProductionRunTask() { + JavaToolchainSpec defaultToolchain = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain(); + getJavaLauncher().convention(getJavaToolchainService().launcherFor(defaultToolchain)); + getRunDir().convention(getProject().getLayout().getProjectDirectory().dir("run")); + + if (!GradleUtils.getBooleanProperty(getProject(), Constants.Properties.DONT_REMAP)) { + getMods().from(getProject().getTasks().named(RemapTaskConfiguration.REMAP_JAR_TASK_NAME)); + } + } + + @TaskAction + public void run() throws IOException { + Files.createDirectories(getRunDir().get().getAsFile().toPath()); + + ExecResult result = getExecOperations().exec(exec -> { + configureCommand(exec); + configureJvmArgs(exec); + configureClasspath(exec); + configureMainClass(exec); + configureProgramArgs(exec); + + exec.setWorkingDir(getRunDir()); + + LOGGER.debug("Running command: {}", exec.getCommandLine()); + }); + result.assertNormalExitValue(); + } + + protected void configureCommand(ExecSpec exec) { + exec.commandLine(getJavaLauncher().get().getExecutablePath()); + } + + protected void configureJvmArgs(ExecSpec exec) { + exec.args(getJvmArgs().get()); + exec.args("-Dfabric.addMods=" + joinFiles(getMods().getFiles().stream())); + } + + protected Stream streamClasspath() { + return getClasspath().getFiles().stream(); + } + + protected void configureClasspath(ExecSpec exec) { + exec.args("-cp"); + exec.args(joinFiles(streamClasspath())); + } + + protected void configureMainClass(ExecSpec exec) { + exec.args(getMainClass().get()); + } + + protected void configureProgramArgs(ExecSpec exec) { + exec.args(getProgramArgs().get()); + } + + @Internal + protected Provider getProjectLoaderVersion() { + return getProject().provider(() -> { + InstallerData installerData = getExtension().getInstallerData(); + + if (installerData == null) { + return null; + } + + return installerData.version(); + }); + } + + protected Provider detachedConfigurationProvider(String mavenNotation, Provider versionProvider) { + return versionProvider.map(version -> { + Dependency serverLauncher = getProject().getDependencies().create(mavenNotation.formatted(version)); + return getProject().getConfigurations().detachedConfiguration(serverLauncher); + }); + } + + private static String joinFiles(Stream stream) { + return stream.map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java b/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java new file mode 100644 index 00000000..f608fea0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java @@ -0,0 +1,93 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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.prod; + +import java.io.File; + +import javax.inject.Inject; + +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.process.ExecSpec; +import org.jetbrains.annotations.ApiStatus; + +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.Platform; + +/** + * A task that runs the Minecraft client in a similar way to a production launcher. You must manually register a task of this type to use it. + */ +@ApiStatus.Experimental +public abstract non-sealed class ClientProductionRunTask extends AbstractProductionRunTask { + // Internal options + @Input + protected abstract Property getAssetsIndex(); + + @InputFiles + protected abstract DirectoryProperty getAssetsDir(); + + @Inject + public ClientProductionRunTask() { + getAssetsIndex().set(getExtension().getMinecraftVersion() + .map(minecraftVersion -> getExtension() + .getMinecraftProvider() + .getVersionInfo() + .assetIndex() + .fabricId(minecraftVersion) + ) + ); + getAssetsDir().set(new File(getExtension().getFiles().getUserCache(), "assets")); + getMainClass().convention("net.fabricmc.loader.impl.launch.knot.KnotClient"); + + getClasspath().from(getExtension().getMinecraftProvider().getMinecraftClientJar()); + getClasspath().from(detachedConfigurationProvider("net.fabricmc:fabric-loader:%s", getProjectLoaderVersion())); + getClasspath().from(detachedConfigurationProvider("net.fabricmc:intermediary:%s", getExtension().getMinecraftVersion())); + getClasspath().from(getProject().getConfigurations().named(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES)); + + dependsOn("downloadAssets"); + } + + @Override + protected void configureJvmArgs(ExecSpec exec) { + super.configureJvmArgs(exec); + + if (Platform.CURRENT.getOperatingSystem().isMacOS()) { + exec.args("-XstartOnFirstThread"); + } + } + + @Override + protected void configureProgramArgs(ExecSpec exec) { + super.configureProgramArgs(exec); + + exec.args( + "--assetIndex", getAssetsIndex().get(), + "--assetsDir", getAssetsDir().get().getAsFile().getAbsolutePath(), + "--gameDir", getRunDir().get().getAsFile().getAbsolutePath() + ); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/prod/ServerProductionRunTask.java b/src/main/java/net/fabricmc/loom/task/prod/ServerProductionRunTask.java new file mode 100644 index 00000000..36a925ca --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/prod/ServerProductionRunTask.java @@ -0,0 +1,108 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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.prod; + +import java.io.File; +import java.io.IOException; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputFile; +import org.jetbrains.annotations.ApiStatus; + +import net.fabricmc.loom.util.LoomVersions; +import net.fabricmc.loom.util.ZipUtils; + +/** + * A task that runs the server using the production server launcher. You must manually register a task of this type to use it. + */ +@ApiStatus.Experimental +public abstract non-sealed class ServerProductionRunTask extends AbstractProductionRunTask { + /** + * The version of Fabric Loader to use. + * + *

Defaults to the version of Fabric Loader that the project is using. + */ + @Input + public abstract Property getLoaderVersion(); + + /** + * The version of Minecraft to use. + * + *

Defaults to the version of Minecraft that the project is using. + */ + @Input + public abstract Property getMinecraftVersion(); + + /** + * The version of the Fabric Installer to use. + * + *

Defaults to a version provided by Loom. + */ + @Input + public abstract Property getInstallerVersion(); + + // Internal options + + @ApiStatus.Internal + @OutputFile + public abstract RegularFileProperty getInstallPropertiesJar(); + + @Inject + public ServerProductionRunTask() { + getLoaderVersion().convention(getProjectLoaderVersion()); + getMinecraftVersion().convention(getExtension().getMinecraftVersion()); + getInstallPropertiesJar().convention(getProject().getLayout().getBuildDirectory().file("server_properties.jar")); + getInstallerVersion().convention(LoomVersions.FABRIC_INSTALLER.version()); + + getMainClass().convention("net.fabricmc.installer.ServerLauncher"); + getClasspath().from(detachedConfigurationProvider("net.fabricmc:fabric-installer:%s:server", getInstallerVersion())); + + getProgramArgs().add("nogui"); + } + + @Override + public void run() throws IOException { + ZipUtils.add( + getInstallPropertiesJar().get().getAsFile().toPath(), + "install.properties", + "fabric-loader-version=%s\ngame-version=%s".formatted(getLoaderVersion().get(), getMinecraftVersion().get()) + ); + + super.run(); + } + + @Override + protected Stream streamClasspath() { + return Stream.concat( + super.streamClasspath(), + Stream.of(getInstallPropertiesJar().get().getAsFile()) + ); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index b197ee99..deceb58d 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -82,6 +82,10 @@ public class Constants { */ public static final String LOCAL_RUNTIME = "localRuntime"; public static final String NAMED_ELEMENTS = "namedElements"; + /** + * The configuration that contains the Minecraft client and loader runtime libraries, as used by the production run tasks. + */ + public static final String MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES = "minecraftTestClientRuntimeLibraries"; private Configurations() { } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy index 0beb66d0..77ee9e50 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2018-2023 FabricMC + * Copyright (c) 2018-2025 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 @@ -24,6 +24,7 @@ package net.fabricmc.loom.test.integration +import spock.lang.IgnoreIf import spock.lang.Specification import spock.lang.Unroll import spock.util.environment.RestoreSystemProperties @@ -130,4 +131,53 @@ class RunConfigTest extends Specification implements GradleProjectTestTrait { where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "prod server (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildGradle << ''' + dependencies { + minecraft "com.mojang:minecraft:1.21.4" + mappings "net.fabricmc:yarn:1.21.4+build.4:v2" + modImplementation "net.fabricmc:fabric-loader:0.16.9" + } + + tasks.register("prodServer", net.fabricmc.loom.task.prod.ServerProductionRunTask) { + installerVersion = "1.0.1" + } + ''' + when: + def result = gradle.run(task: "prodServer") + + then: + result.task(":prodServer").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } + + @Unroll + @IgnoreIf({ System.getenv("CI") != null }) // This test is disabled on CI because it launches a real client and cannot run headless. + def "prod client (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildGradle << ''' + dependencies { + minecraft "com.mojang:minecraft:1.21.4" + mappings "net.fabricmc:yarn:1.21.4+build.4:v2" + modImplementation "net.fabricmc:fabric-loader:0.16.9" + } + + tasks.register("prodClient", net.fabricmc.loom.task.prod.ClientProductionRunTask) + ''' + when: + def result = gradle.run(task: "prodClient") + + then: + result.task(":prodClient").outcome == SUCCESS + + where: + version << STANDARD_TEST_VERSIONS + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy b/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy index 805c2094..38d13525 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/ServerRunner.groovy @@ -29,11 +29,12 @@ import java.util.concurrent.TimeUnit import groovy.transform.Immutable import net.fabricmc.loom.test.LoomTestVersions +import net.fabricmc.loom.util.LoomVersions import net.fabricmc.loom.util.download.Download class ServerRunner { static final String LOADER_VERSION = LoomTestVersions.FABRIC_LOADER.version() - static final String INSTALLER_VERSION = LoomTestVersions.FABRIC_INSTALLER.version() + static final String INSTALLER_VERSION = LoomVersions.FABRIC_INSTALLER.version() static final Map FABRIC_API_URLS = [ "1.16.5": "https://github.com/FabricMC/fabric/releases/download/0.37.1%2B1.16/fabric-api-0.37.1+1.16.jar", "1.17.1": "https://github.com/FabricMC/fabric/releases/download/0.37.1%2B1.17/fabric-api-0.37.1+1.17.jar" From 52a19b3bf752907105145355011dedc190329d2d Mon Sep 17 00:00:00 2001 From: modmuss Date: Thu, 2 Jan 2025 20:35:00 +0000 Subject: [PATCH 11/25] Use XVFB on Linux CI, allows running the client prod tasks on a headless OS (#1243) --- build.gradle | 7 ++++- .../task/prod/ClientProductionRunTask.java | 31 +++++++++++++++++++ .../test/integration/RunConfigTest.groovy | 18 +++++++++-- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index d63f0436..db58217e 100644 --- a/build.gradle +++ b/build.gradle @@ -270,6 +270,11 @@ test { maxRetries = 3 } } + + testLogging { + // Log everything to the console + setEvents(TestLogEvent.values().toList()) + } } // Workaround https://github.com/gradle/gradle/issues/25898 @@ -283,7 +288,7 @@ tasks.withType(Test).configureEach { } -import org.gradle.api.internal.artifacts.configurations.ConfigurationRoles +import org.gradle.api.tasks.testing.logging.TestLogEvent import org.gradle.util.GradleVersion import org.w3c.dom.Document import org.w3c.dom.Element diff --git a/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java b/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java index f608fea0..01779a85 100644 --- a/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java @@ -43,6 +43,16 @@ import net.fabricmc.loom.util.Platform; */ @ApiStatus.Experimental public abstract non-sealed class ClientProductionRunTask extends AbstractProductionRunTask { + /** + * Whether to use XVFB to run the game, using a virtual framebuffer. This is useful for CI environments that don't have a display server. + * + *

Defaults to true only on Linux and when the "CI" environment variable is set. + * + *

XVFB must be installed, on Debian-based systems you can install it with: apt install -y xvfb + */ + @Input + public abstract Property getUseXVFB(); + // Internal options @Input protected abstract Property getAssetsIndex(); @@ -52,6 +62,11 @@ public abstract non-sealed class ClientProductionRunTask extends AbstractProduct @Inject public ClientProductionRunTask() { + getUseXVFB().convention(getProject().getProviders().environmentVariable("CI") + .map(value -> Platform.CURRENT.getOperatingSystem().isLinux()) + .orElse(false) + ); + getAssetsIndex().set(getExtension().getMinecraftVersion() .map(minecraftVersion -> getExtension() .getMinecraftProvider() @@ -71,6 +86,22 @@ public abstract non-sealed class ClientProductionRunTask extends AbstractProduct dependsOn("downloadAssets"); } + @Override + protected void configureCommand(ExecSpec exec) { + if (getUseXVFB().get()) { + if (!Platform.CURRENT.getOperatingSystem().isLinux()) { + throw new UnsupportedOperationException("XVFB is only supported on Linux"); + } + + exec.commandLine("/usr/bin/xvfb-run"); + exec.args("-a", getJavaLauncher().get().getExecutablePath()); + + return; + } + + super.configureCommand(exec); + } + @Override protected void configureJvmArgs(ExecSpec exec) { super.configureJvmArgs(exec); diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy index 77ee9e50..95b8f011 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy @@ -24,8 +24,11 @@ package net.fabricmc.loom.test.integration +import java.util.concurrent.TimeUnit + import spock.lang.IgnoreIf import spock.lang.Specification +import spock.lang.Timeout import spock.lang.Unroll import spock.util.environment.RestoreSystemProperties @@ -157,19 +160,30 @@ class RunConfigTest extends Specification implements GradleProjectTestTrait { version << STANDARD_TEST_VERSIONS } + @Timeout(value = 10, unit = TimeUnit.MINUTES) @Unroll - @IgnoreIf({ System.getenv("CI") != null }) // This test is disabled on CI because it launches a real client and cannot run headless. + @IgnoreIf({ !os.linux }) // XVFB is installed on the CI for this test def "prod client (gradle #version)"() { setup: def gradle = gradleProject(project: "minimalBase", version: version) gradle.buildGradle << ''' + configurations { + productionMods + } + dependencies { minecraft "com.mojang:minecraft:1.21.4" mappings "net.fabricmc:yarn:1.21.4+build.4:v2" modImplementation "net.fabricmc:fabric-loader:0.16.9" + modImplementation "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4" + + productionMods "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4" } - tasks.register("prodClient", net.fabricmc.loom.task.prod.ClientProductionRunTask) + tasks.register("prodClient", net.fabricmc.loom.task.prod.ClientProductionRunTask) { + mods.from(configurations.productionMods) + jvmArgs.add("-Dfabric.client.gametest") + } ''' when: def result = gradle.run(task: "prodClient") From e1cc6f05fa8ef5f52c9e3336f96e76f51dd01f5d Mon Sep 17 00:00:00 2001 From: modmuss Date: Sat, 4 Jan 2025 21:06:28 +0000 Subject: [PATCH 12/25] Add support for running the production client with the tracy profiler. (#1244) * Add support for running the production client with the tracy profiler. * Fix test * Update tracy capture --- .../task/prod/ClientProductionRunTask.java | 33 ++++ .../fabricmc/loom/task/prod/TracyCapture.java | 159 ++++++++++++++++++ .../test/integration/RunConfigTest.groovy | 21 ++- 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/fabricmc/loom/task/prod/TracyCapture.java diff --git a/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java b/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java index 01779a85..6a8ff514 100644 --- a/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/prod/ClientProductionRunTask.java @@ -25,13 +25,17 @@ package net.fabricmc.loom.task.prod; import java.io.File; +import java.io.IOException; import javax.inject.Inject; +import org.gradle.api.Action; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; import org.gradle.process.ExecSpec; import org.jetbrains.annotations.ApiStatus; @@ -53,6 +57,21 @@ public abstract non-sealed class ClientProductionRunTask extends AbstractProduct @Input public abstract Property getUseXVFB(); + @Nested + @Optional + public abstract Property getTracyCapture(); + + /** + * Configures the tracy profiler to run alongside the game. See @{@link TracyCapture} for more information. + * + * @param action The configuration action. + */ + public void tracy(Action action) { + getTracyCapture().set(getProject().getObjects().newInstance(TracyCapture.class)); + getTracyCapture().finalizeValue(); + action.execute(getTracyCapture().get()); + } + // Internal options @Input protected abstract Property getAssetsIndex(); @@ -86,6 +105,16 @@ public abstract non-sealed class ClientProductionRunTask extends AbstractProduct dependsOn("downloadAssets"); } + @Override + public void run() throws IOException { + if (getTracyCapture().isPresent()) { + getTracyCapture().get().runWithTracy(super::run); + return; + } + + super.run(); + } + @Override protected void configureCommand(ExecSpec exec) { if (getUseXVFB().get()) { @@ -120,5 +149,9 @@ public abstract non-sealed class ClientProductionRunTask extends AbstractProduct "--assetsDir", getAssetsDir().get().getAsFile().getAbsolutePath(), "--gameDir", getRunDir().get().getAsFile().getAbsolutePath() ); + + if (getTracyCapture().isPresent()) { + exec.args("--tracy"); + } } } diff --git a/src/main/java/net/fabricmc/loom/task/prod/TracyCapture.java b/src/main/java/net/fabricmc/loom/task/prod/TracyCapture.java new file mode 100644 index 00000000..caff218e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/prod/TracyCapture.java @@ -0,0 +1,159 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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.prod; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.function.Consumer; + +import javax.inject.Inject; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.util.ExceptionUtil; + +public abstract class TracyCapture { + private static final Logger LOGGER = LoggerFactory.getLogger(TracyCapture.class); + + /** + * The path to the tracy-capture executable. + */ + @InputFile + @Optional + public abstract RegularFileProperty getTracyCapture(); + + /** + * The maximum number of seconds to wait for tracy-capture to stop on its own before killing it. + * + *

Defaults to 10 seconds. + */ + @Input + public abstract Property getMaxShutdownWaitSeconds(); + + /** + * The path to the output file. + */ + @OutputFile + @Optional + public abstract RegularFileProperty getOutput(); + + @Inject + public TracyCapture() { + getMaxShutdownWaitSeconds().convention(10); + } + + void runWithTracy(IORunnable runnable) throws IOException { + TracyCaptureRunner tracyCaptureRunner = createRunner(); + + boolean success = false; + + try { + runnable.run(); + success = true; + } finally { + try { + tracyCaptureRunner.close(); + } catch (Exception e) { + if (success) { + //noinspection ThrowFromFinallyBlock + throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to stop tracy capture", e); + } + } + } + } + + private TracyCaptureRunner createRunner() throws IOException { + File tracyCapture = getTracyCapture().getAsFile().get(); + File output = getOutput().getAsFile().get(); + + ProcessBuilder builder = new ProcessBuilder() + .command(tracyCapture.getAbsolutePath(), "-a", "127.0.0.1", "-f", "-o", output.getAbsolutePath()); + Process process = builder.start(); + + captureLog(process.getInputStream(), LOGGER::info); + captureLog(process.getErrorStream(), LOGGER::error); + + LOGGER.info("Tracy capture started"); + + return new TracyCaptureRunner(process, getMaxShutdownWaitSeconds().get()); + } + + private record TracyCaptureRunner(Process process, int shutdownWait) implements AutoCloseable { + @Override + public void close() throws Exception { + // Wait x seconds for tracy to stop on its own + // This allows time for tracy to save the profile to disk + for (int i = 0; i < shutdownWait; i++) { + if (!process.isAlive()) { + break; + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + // If it's still running, kill it + if (process.isAlive()) { + LOGGER.error("Tracy capture did not stop on its own, killing it"); + process.destroy(); + process.waitFor(); + } + + int exitCode = process.exitValue(); + + if (exitCode != 0) { + throw new RuntimeException("Tracy capture failed with exit code " + exitCode); + } + } + } + + private static void captureLog(InputStream inputStream, Consumer lineConsumer) { + new Thread(() -> { + try { + new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(lineConsumer); + } catch (Exception e) { + // Don't really care, this will happen when the stream is closed + } + }).start(); + } + + @FunctionalInterface + public interface IORunnable { + void run() throws IOException; + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy index 95b8f011..f0f86bfb 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy @@ -32,20 +32,24 @@ import spock.lang.Timeout import spock.lang.Unroll import spock.util.environment.RestoreSystemProperties +import net.fabricmc.loom.test.LoomTestConstants import net.fabricmc.loom.test.util.GradleProjectTestTrait +import net.fabricmc.loom.util.download.Download import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS import static org.gradle.testkit.runner.TaskOutcome.SUCCESS // This test runs a mod that exits on mod init class RunConfigTest extends Specification implements GradleProjectTestTrait { - private static List tasks = [ + private static final List tasks = [ "runClient", "runServer", "runTestmodClient", "runTestmodServer", "runAutoTestServer" ] + private static final String TRACY_CAPTURE_LINUX = "https://github.com/modmuss50/tracy-utils/releases/download/0.0.2/linux-x86_64-tracy-capture" + @Unroll def "Run config #task (gradle #version)"() { setup: @@ -165,6 +169,9 @@ class RunConfigTest extends Specification implements GradleProjectTestTrait { @IgnoreIf({ !os.linux }) // XVFB is installed on the CI for this test def "prod client (gradle #version)"() { setup: + def tracyCapture = new File(LoomTestConstants.TEST_DIR, "tracy-capture") + Download.create(TRACY_CAPTURE_LINUX).defaultCache().downloadPath(tracyCapture.toPath()) + def gradle = gradleProject(project: "minimalBase", version: version) gradle.buildGradle << ''' configurations { @@ -183,13 +190,25 @@ class RunConfigTest extends Specification implements GradleProjectTestTrait { tasks.register("prodClient", net.fabricmc.loom.task.prod.ClientProductionRunTask) { mods.from(configurations.productionMods) jvmArgs.add("-Dfabric.client.gametest") + + tracy { + tracyCapture = file("tracy-capture") + output = file("profile.tracy") + } } ''' + + // Copy tracy into the project + def projectTracyCapture = new File(gradle.projectDir, "tracy-capture") + projectTracyCapture.bytes = tracyCapture.bytes + projectTracyCapture.setExecutable(true) + when: def result = gradle.run(task: "prodClient") then: result.task(":prodClient").outcome == SUCCESS + new File(gradle.projectDir, "profile.tracy").exists() where: version << STANDARD_TEST_VERSIONS From 3ee1372febc953c31d46abebc553ca1e082f46a9 Mon Sep 17 00:00:00 2001 From: modmuss Date: Wed, 8 Jan 2025 09:51:42 +0000 Subject: [PATCH 13/25] Fix getCompileRuntimeModsFromRemapConfigs not returning mods that have different versions on the compile/runtime classpath. (#1246) --- .../processors/SpecContextImpl.java | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java b/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java index d9d2aac8..e8496be2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.gradle.api.Project; @@ -42,6 +43,7 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.attributes.Usage; import org.gradle.api.plugins.JavaPlugin; +import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.RemapConfigurationSettings; @@ -121,27 +123,37 @@ public record SpecContextImpl(List modDependencies, List getCompileRuntimeModsFromRemapConfigs(Project project, Map> fmjCache) { final LoomGradleExtension extension = LoomGradleExtension.get(project); - final List runtimeEntries = extension.getRuntimeRemapConfigurations().stream() + final Set runtimeModIds = extension.getRuntimeRemapConfigurations().stream() .filter(settings -> settings.getApplyDependencyTransforms().get()) .flatMap(resolveArtifacts(project, true)) - .toList(); + .map(modFromZip(fmjCache)) + .filter(Objects::nonNull) + .map(FabricModJson::getId) + .collect(Collectors.toSet()); return extension.getCompileRemapConfigurations().stream() .filter(settings -> settings.getApplyDependencyTransforms().get()) - .flatMap(resolveArtifacts(project, false)) - .filter(runtimeEntries::contains) // Use the intersection of the two configurations. - .map(zipPath -> { - final List list = fmjCache.computeIfAbsent(zipPath.toAbsolutePath().toString(), $ -> { - return FabricModJsonFactory.createFromZipOptional(zipPath) - .map(List::of) - .orElseGet(List::of); - }); - return list.isEmpty() ? null : list.get(0); - }) + .flatMap(resolveArtifacts(project, false))// Use the intersection of the two configurations. + .map(modFromZip(fmjCache)) .filter(Objects::nonNull) + // Only check based on the modid, as there may be differing versions used between the compile and runtime classpath. + // We assume that the version used at runtime will be binary compatible with the version used to compile against. + // It's not perfect but better than silently not supplying the mod, and this could happen with regular API that you compile against anyway. + .filter(fabricModJson -> runtimeModIds.contains(fabricModJson.getId())) .sorted(Comparator.comparing(FabricModJson::getId)); } + private static Function modFromZip(Map> fmjCache) { + return zipPath -> { + final List list = fmjCache.computeIfAbsent(zipPath.toAbsolutePath().toString(), $ -> { + return FabricModJsonFactory.createFromZipOptional(zipPath) + .map(List::of) + .orElseGet(List::of); + }); + return list.isEmpty() ? null : list.get(0); + }; + } + private static Function> resolveArtifacts(Project project, boolean runtime) { final Usage usage = project.getObjects().named(Usage.class, runtime ? Usage.JAVA_RUNTIME : Usage.JAVA_API); From 362fc98c47ce5b839e87148d54ef75db48abbf93 Mon Sep 17 00:00:00 2001 From: modmuss Date: Wed, 22 Jan 2025 22:05:55 +0000 Subject: [PATCH 14/25] Improvements to test and IDE runs (#1252) * Improves to test and IDE runs * Use correct sourceset --- .../loom/api/fabricapi/GameTestSettings.java | 10 ++++++++ .../fabricapi/FabricApiAbstractSourceSet.java | 14 ++++++----- .../fabricapi/FabricApiTesting.java | 23 ++++++++++++++++++- .../configuration/ide/idea/IdeaSyncTask.java | 2 ++ 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java b/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java index 5675484a..1e556191 100644 --- a/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java +++ b/src/main/java/net/fabricmc/loom/api/fabricapi/GameTestSettings.java @@ -79,4 +79,14 @@ public interface GameTestSettings { *

Default: true */ Property getClearRunDirectory(); + + /** + * Contains a string property representing the username to use for the client side game tests. + * + *

This only works when {@link #getEnableClientGameTests()} is enabled. + * + *

Default: Player0 + */ + @Optional + Property getUsername(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java index 6ffdce0c..22bec596 100644 --- a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiAbstractSourceSet.java @@ -46,7 +46,7 @@ abstract class FabricApiAbstractSourceSet { protected abstract String getSourceSetName(); - protected void configureSourceSet(Property modId, boolean isClient) { + protected SourceSet configureSourceSet(Property modId, boolean isClient) { final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); final SourceSet mainSourceSet = SourceSetHelper.getMainSourceSet(getProject()); @@ -54,18 +54,18 @@ abstract class FabricApiAbstractSourceSet { SourceSetContainer sourceSets = SourceSetHelper.getSourceSets(getProject()); - // Create the new datagen sourceset, depend on the main or client sourceset. - SourceSet dataGenSourceSet = sourceSets.create(getSourceSetName(), sourceSet -> { - dependsOn(sourceSet, mainSourceSet); + // Create the new sourceset, depend on the main or client sourceset. + SourceSet sourceSet = sourceSets.create(getSourceSetName(), ss -> { + dependsOn(ss, mainSourceSet); if (isClientAndSplit) { - dependsOn(sourceSet, SourceSetHelper.getSourceSetByName(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME, getProject())); + dependsOn(ss, SourceSetHelper.getSourceSetByName(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME, getProject())); } }); modId.convention(getProject().provider(() -> { try { - final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), dataGenSourceSet); + final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), sourceSet); if (fabricModJson == null) { throw new RuntimeException("Could not find a fabric.mod.json file in the data source set or a value for DataGenerationSettings.getModId()"); @@ -83,6 +83,8 @@ abstract class FabricApiAbstractSourceSet { }); extension.createRemapConfigurations(sourceSets.getByName(getSourceSetName())); + + return sourceSet; } private static void extendsFrom(Project project, String name, String extendsFrom) { diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java index c3585db3..23084076 100644 --- a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java @@ -24,6 +24,7 @@ package net.fabricmc.loom.configuration.fabricapi; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -36,6 +37,7 @@ import org.gradle.api.Project; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.Delete; import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskContainer; @@ -45,6 +47,7 @@ import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.task.AbstractLoomTask; import net.fabricmc.loom.task.LoomTasks; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.gradle.SourceSetHelper; public abstract class FabricApiTesting extends FabricApiAbstractSourceSet { @Inject @@ -69,11 +72,16 @@ public abstract class FabricApiTesting extends FabricApiAbstractSourceSet { settings.getEnableClientGameTests().convention(true); settings.getEula().convention(false); settings.getClearRunDirectory().convention(true); + settings.getUsername().convention("Player0"); action.execute(settings); + final SourceSet testSourceSet; + if (settings.getCreateSourceSet().get()) { - configureSourceSet(settings.getModId(), true); + testSourceSet = configureSourceSet(settings.getModId(), true); + } else { + testSourceSet = SourceSetHelper.getMainSourceSet(getProject()); } Consumer configureBase = run -> { @@ -94,10 +102,23 @@ public abstract class FabricApiTesting extends FabricApiAbstractSourceSet { } if (settings.getEnableClientGameTests().get()) { + // Not ideal as there may be multiple resources directories, if this isnt correct the mod will need to override this. + final File resourcesDir = testSourceSet.getResources().getFiles().stream().findAny().orElse(null); + RunConfigSettings clientGameTest = extension.getRunConfigs().create("clientGameTest", run -> { run.inherit(extension.getRunConfigs().getByName("client")); run.property("fabric.client.gametest"); + + if (resourcesDir != null) { + run.property("fabric.client.gametest.testModResourcesPath", resourcesDir.getAbsolutePath()); + } + run.runDir("build/run/clientGameTest"); + + if (settings.getUsername().isPresent()) { + run.programArgs("--username", settings.getUsername().get()); + } + configureBase.accept(run); }); diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaSyncTask.java b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaSyncTask.java index 4fae7e3b..f3a55e8a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaSyncTask.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/idea/IdeaSyncTask.java @@ -110,6 +110,8 @@ public abstract class IdeaSyncTask extends AbstractLoomTask { irc.getExcludedLibraryPaths().set(excludedLibraryPaths); irc.getLaunchFile().set(runConfigFile); configs.add(irc); + + settings.makeRunDir(); } return configs; From cb8ba8fe1d15c9bb58d64e16dd261d2278ea253d Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 26 Jan 2025 12:35:26 +0000 Subject: [PATCH 15/25] Correct use UntrackedTask. Also fix #1254 --- .../java/net/fabricmc/loom/task/GenerateSourcesTask.java | 5 ++--- src/main/java/net/fabricmc/loom/task/LoomTasks.java | 1 - .../java/net/fabricmc/loom/task/MigrateMappingsTask.java | 4 ++-- .../fabricmc/loom/task/prod/AbstractProductionRunTask.java | 2 ++ 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index f0f087bd..cf12eeea 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -63,11 +63,11 @@ import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.UntrackedTask; import org.gradle.api.tasks.options.Option; import org.gradle.internal.logging.progress.ProgressLoggerFactory; import org.gradle.process.ExecOperations; import org.gradle.process.ExecResult; -import org.gradle.work.DisableCachingByDefault; import org.gradle.workers.WorkAction; import org.gradle.workers.WorkParameters; import org.gradle.workers.WorkQueue; @@ -104,7 +104,7 @@ import net.fabricmc.loom.util.ipc.IPCServer; import net.fabricmc.loom.util.service.ScopedServiceFactory; import net.fabricmc.mappingio.tree.MemoryMappingTree; -@DisableCachingByDefault +@UntrackedTask(because = "Manually invoked, has internal caching") public abstract class GenerateSourcesTask extends AbstractLoomTask { private static final String CACHE_VERSION = "v1"; private final DecompilerOptions decompilerOptions; @@ -237,7 +237,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { throw new IllegalStateException("Input minecraft jar not found: " + getInputJarName().get()); })); - getOutputs().upToDateWhen((o) -> false); getClasspath().from(decompilerOptions.getClasspath()).finalizeValueOnRead(); dependsOn(decompilerOptions.getClasspath().getBuiltBy()); diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 27ddf591..97d4df71 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -54,7 +54,6 @@ public abstract class LoomTasks implements Runnable { public void run() { getTasks().register("migrateMappings", MigrateMappingsTask.class, t -> { t.setDescription("Migrates mappings to a new version."); - t.getOutputs().upToDateWhen(o -> false); }); var generateLog4jConfig = getTasks().register("generateLog4jConfig", GenerateLog4jConfigTask.class, t -> { diff --git a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java index 123a5404..0024570d 100644 --- a/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java +++ b/src/main/java/net/fabricmc/loom/task/MigrateMappingsTask.java @@ -31,13 +31,13 @@ import org.gradle.api.tasks.InputDirectory; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.UntrackedTask; import org.gradle.api.tasks.options.Option; -import org.gradle.work.DisableCachingByDefault; import net.fabricmc.loom.task.service.MigrateMappingsService; import net.fabricmc.loom.util.service.ScopedServiceFactory; -@DisableCachingByDefault(because = "Always rerun this task.") +@UntrackedTask(because = "Always rerun this task.") public abstract class MigrateMappingsTask extends AbstractLoomTask { @Input @Option(option = "mappings", description = "Target mappings") diff --git a/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java b/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java index 15c45439..0661f969 100644 --- a/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java @@ -46,6 +46,7 @@ import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.UntrackedTask; import org.gradle.jvm.toolchain.JavaLauncher; import org.gradle.jvm.toolchain.JavaToolchainService; import org.gradle.jvm.toolchain.JavaToolchainSpec; @@ -68,6 +69,7 @@ import net.fabricmc.loom.util.gradle.GradleUtils; *

Do not use this task directly, use {@link ClientProductionRunTask} or {@link ServerProductionRunTask} instead. */ @ApiStatus.Experimental +@UntrackedTask(because = "Always rerun this task.") public abstract sealed class AbstractProductionRunTask extends AbstractLoomTask permits ClientProductionRunTask, ServerProductionRunTask { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractProductionRunTask.class); From 9bcce94d5ad02716cdcb0350345c3e78c47d57b7 Mon Sep 17 00:00:00 2001 From: modmuss Date: Sun, 26 Jan 2025 14:02:16 +0000 Subject: [PATCH 16/25] Update to Mapping-IO 0.7.0 & Tiny Remapper 0.11 (#1245) * Update to Mapping-IO 0.7.0 * Update to Mapping-IO 0.7.1 * Revert changes * Update tiny remapper * Fix invalid tiny v2 header. --- gradle/libs.versions.toml | 4 +- .../loom/configuration/mods/ModProcessor.java | 3 +- .../NoOpIntermediateMappingsProvider.java | 2 +- .../minecraft/SingleJarMinecraftProvider.java | 3 +- .../loom/task/ValidateAccessWidenerTask.java | 3 +- .../task/service/TinyRemapperService.java | 3 +- .../loom/util/TinyRemapperHelper.java | 2 +- .../loom/util/TinyRemapperLoggerAdapter.java | 57 +++++++++++++++++++ 8 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/util/TinyRemapperLoggerAdapter.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 23f5f136..8be092fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,9 +6,9 @@ gson = "2.10.1" guava = "33.0.0-jre" stitch = "0.6.2" -tiny-remapper = "0.10.4" +tiny-remapper = "0.11.0" access-widener = "2.1.0" -mapping-io = "0.6.1" +mapping-io = "0.7.1" lorenz-tiny = "4.0.2" mercury = "0.4.2" loom-native = "0.2.0" diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index b9e7b8cc..32f5f66d 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -55,6 +55,7 @@ import net.fabricmc.loom.extension.RemapperExtensionHolder; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Pair; import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.loom.util.TinyRemapperLoggerAdapter; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.kotlin.KotlinClasspathService; import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader; @@ -139,7 +140,7 @@ public class ModProcessor { knownIndyBsms.addAll(modDependency.getMetadata().knownIdyBsms()); } - TinyRemapper.Builder builder = TinyRemapper.newRemapper() + TinyRemapper.Builder builder = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE) .withKnownIndyBsm(knownIndyBsms) .withMappings(TinyRemapperHelper.create(mappingConfiguration.getMappingsService(project, serviceFactory).getMappingTree(), fromM, toM, false)) .renameInvalidLocals(false) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/NoOpIntermediateMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/NoOpIntermediateMappingsProvider.java index d198baf9..f1c6b997 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/NoOpIntermediateMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/NoOpIntermediateMappingsProvider.java @@ -38,7 +38,7 @@ import net.fabricmc.loom.api.mappings.intermediate.IntermediateMappingsProvider; */ public abstract class NoOpIntermediateMappingsProvider extends IntermediateMappingsProvider { private static final String HEADER_OFFICIAL_MERGED = "tiny\t2\t0\tofficial\tintermediary"; - private static final String HEADER_OFFICIAL_LEGACY_MERGED = "tiny\t2\t0\tintermediary\tclientOfficial\tserverOfficial\t"; + private static final String HEADER_OFFICIAL_LEGACY_MERGED = "tiny\t2\t0\tintermediary\tclientOfficial\tserverOfficial"; @Override public void provide(Path tinyMappings) throws IOException { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java index 76b06564..c96564fa 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java @@ -32,6 +32,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.ConfigContext; import net.fabricmc.loom.configuration.providers.BundleMetadata; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.TinyRemapperLoggerAdapter; import net.fabricmc.tinyremapper.NonClassCopyMode; import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.TinyRemapper; @@ -94,7 +95,7 @@ public abstract sealed class SingleJarMinecraftProvider extends MinecraftProvide TinyRemapper remapper = null; try { - remapper = TinyRemapper.newRemapper().build(); + remapper = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE).build(); Files.deleteIfExists(minecraftEnvOnlyJar); diff --git a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java index 08a46c7c..bc2f2d73 100644 --- a/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java +++ b/src/main/java/net/fabricmc/loom/task/ValidateAccessWidenerTask.java @@ -46,6 +46,7 @@ import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.accesswidener.AccessWidenerVisitor; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.util.TinyRemapperLoggerAdapter; import net.fabricmc.tinyremapper.TinyRemapper; import net.fabricmc.tinyremapper.api.TrEnvironment; @@ -70,7 +71,7 @@ public abstract class ValidateAccessWidenerTask extends DefaultTask { @TaskAction public void run() { - final TinyRemapper tinyRemapper = TinyRemapper.newRemapper().build(); + final TinyRemapper tinyRemapper = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE).build(); for (File file : getTargetJars().getFiles()) { tinyRemapper.readClassPath(file.toPath()); 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 7f0ae8d1..21752ea6 100644 --- a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -54,6 +54,7 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.extension.RemapperExtensionHolder; import net.fabricmc.loom.task.AbstractRemapJarTask; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.TinyRemapperLoggerAdapter; import net.fabricmc.loom.util.kotlin.KotlinClasspathService; import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader; import net.fabricmc.loom.util.service.Service; @@ -130,7 +131,7 @@ public class TinyRemapperService extends Service im } private TinyRemapper createTinyRemapper() { - TinyRemapper.Builder builder = TinyRemapper.newRemapper() + TinyRemapper.Builder builder = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE) .withKnownIndyBsm(Set.copyOf(getOptions().getKnownIndyBsms().get())); for (MappingsService.Options options : getOptions().getMappings().get()) { diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index 4024d4bf..4482aa9d 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -74,7 +74,7 @@ public final class TinyRemapperHelper { int intermediaryNsId = mappingTree.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString()); - TinyRemapper.Builder builder = TinyRemapper.newRemapper() + TinyRemapper.Builder builder = TinyRemapper.newRemapper(TinyRemapperLoggerAdapter.INSTANCE) .withMappings(create(mappingTree, fromM, toM, true)) .withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass)) .renameInvalidLocals(true) diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperLoggerAdapter.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperLoggerAdapter.java new file mode 100644 index 00000000..8bc9872d --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperLoggerAdapter.java @@ -0,0 +1,57 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.tinyremapper.api.TrLogger; + +public final class TinyRemapperLoggerAdapter implements TrLogger { + public static final TinyRemapperLoggerAdapter INSTANCE = new TinyRemapperLoggerAdapter(); + + private static final Logger LOGGER = LoggerFactory.getLogger("TinyRemapper"); + + private TinyRemapperLoggerAdapter() { + } + + @Override + public void log(Level level, String message) { + switch (level) { + case ERROR: + LOGGER.error(message); + break; + case WARN: + LOGGER.warn(message); + break; + case INFO: + LOGGER.info(message); + break; + case DEBUG: + LOGGER.debug(message); + break; + } + } +} From 1c025787b9b31bbabfde245f7ca1afebeba3652b Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 26 Jan 2025 14:26:41 +0000 Subject: [PATCH 17/25] Fix VSC working directory location. Co-authored-by: fewizz --- .../net/fabricmc/loom/task/GenVsCodeProjectTask.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java index 6684e5ce..398bba75 100644 --- a/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenVsCodeProjectTask.java @@ -118,7 +118,8 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask { } for (VsCodeConfiguration configuration : getLaunchConfigurations().get()) { - final JsonElement configurationJson = LoomGradlePlugin.GSON.toJsonTree(configuration); + JsonObject configurationJson = LoomGradlePlugin.GSON.toJsonTree(configuration).getAsJsonObject(); + configurationJson.remove("runDir"); final List toRemove = new LinkedList<>(); @@ -161,11 +162,14 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask { String projectName, String runDir) implements Serializable { public static VsCodeConfiguration fromRunConfig(Project project, RunConfig runConfig) { + Path rootPath = project.getRootDir().toPath(); + Path projectPath = project.getProjectDir().toPath(); + String relativeRunDir = rootPath.relativize(projectPath).resolve(runConfig.runDir).toString(); return new VsCodeConfiguration( "java", runConfig.configName, "launch", - "${workspaceFolder}/" + runConfig.runDir, + "${workspaceFolder}/" + relativeRunDir, "integratedTerminal", false, runConfig.mainClass, @@ -173,7 +177,7 @@ public abstract class GenVsCodeProjectTask extends AbstractLoomTask { RunConfig.joinArguments(runConfig.programArgs), new HashMap<>(runConfig.environmentVariables), runConfig.projectName, - project.getProjectDir().toPath().resolve(runConfig.runDir).toAbsolutePath().toString() + rootPath.resolve(relativeRunDir).toAbsolutePath().toString() ); } } From cd6e7a3e284d797138f49127b665cad0eb2982f8 Mon Sep 17 00:00:00 2001 From: modmuss Date: Sun, 26 Jan 2025 18:30:37 +0000 Subject: [PATCH 18/25] Run tests with latest nightly (#1255) --- gradle/test.libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index 357c1c28..b609e5c2 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -6,7 +6,7 @@ mockito = "5.14.2" java-debug = "0.52.0" mixin = "0.15.3+mixin.0.8.7" -gradle-nightly = "8.13-20241222002427+0000" +gradle-nightly = "8.13-20250125001926+0000" fabric-loader = "0.16.9" [libraries] From fc26023047c78b02c29659f5d3650a2a288bd0d9 Mon Sep 17 00:00:00 2001 From: modmuss Date: Sun, 26 Jan 2025 21:06:30 +0000 Subject: [PATCH 19/25] Configure compile task mixin options lazily (#1256) * Try to configure compile task mixin options lazily * Fix CompileJava being realised too soon, and other misc lazy task fixes. --- .../mixin/AnnotationProcessorInvoker.java | 11 ++--- .../loom/build/mixin/GroovyApInvoker.java | 11 +++-- .../loom/build/mixin/JavaApInvoker.java | 10 ++--- .../loom/build/mixin/KaptApInvoker.java | 40 ++++++++++--------- .../loom/build/mixin/ScalaApInvoker.java | 10 ++--- .../loom/extension/MixinExtension.java | 5 ++- .../loom/extension/MixinExtensionImpl.java | 10 +++-- .../net/fabricmc/loom/task/RemapJarTask.java | 2 +- .../loom/task/RemapTaskConfiguration.java | 4 +- .../task/service/SourceRemapperService.java | 30 +++++++++++++- .../fabricmc/loom/util/SourceRemapper.java | 31 +------------- .../projects/mixinApSimple/build.gradle | 5 ++- 12 files changed, 89 insertions(+), 80 deletions(-) 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 c1f93855..f8769b2a 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2020-2022 FabricMC + * Copyright (c) 2020-2025 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 @@ -40,6 +40,7 @@ import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.idea.IdeaUtils; @@ -64,13 +65,13 @@ public abstract class AnnotationProcessorInvoker { protected final Project project; private final LoomGradleExtension loomExtension; protected final MixinExtension mixinExtension; - protected final Map invokerTasks; + protected final Map> invokerTasks; private final String name; private final Collection apConfigurations; protected AnnotationProcessorInvoker(Project project, Collection apConfigurations, - Map invokerTasks, String name) { + Map> invokerTasks, String name) { this.project = project; this.loomExtension = LoomGradleExtension.get(project); this.mixinExtension = loomExtension.getMixin(); @@ -146,8 +147,8 @@ public abstract class AnnotationProcessorInvoker { } } - for (Map.Entry entry : invokerTasks.entrySet()) { - passMixinArguments(entry.getValue(), entry.getKey()); + for (Map.Entry> entry : invokerTasks.entrySet()) { + entry.getValue().configure(t -> passMixinArguments(t, entry.getKey())); } } diff --git a/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java index ab85b465..742bab84 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.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-2025 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 @@ -26,12 +26,12 @@ package net.fabricmc.loom.build.mixin; import java.io.File; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import com.google.common.collect.ImmutableList; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.compile.GroovyCompile; import net.fabricmc.loom.LoomGradleExtension; @@ -46,11 +46,10 @@ public class GroovyApInvoker extends AnnotationProcessorInvoker { AnnotationProcessorInvoker.GROOVY); } - private static Map getInvokerTasks(Project project) { + private static Map> getInvokerTasks(Project project) { MixinExtension mixin = LoomGradleExtension.get(project).getMixin(); - return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.GROOVY).collect( - Collectors.toMap(Map.Entry::getKey, - entry -> Objects.requireNonNull((GroovyCompile) entry.getValue()))); + return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.GROOVY, GroovyCompile.class).collect( + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override diff --git a/src/main/java/net/fabricmc/loom/build/mixin/JavaApInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/JavaApInvoker.java index 5450009d..1327fcf2 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/JavaApInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/JavaApInvoker.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2022 FabricMC + * Copyright (c) 2016-2025 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 @@ -26,11 +26,11 @@ package net.fabricmc.loom.build.mixin; import java.io.File; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.compile.JavaCompile; import net.fabricmc.loom.LoomGradleExtension; @@ -45,10 +45,10 @@ public class JavaApInvoker extends AnnotationProcessorInvoker { AnnotationProcessorInvoker.JAVA); } - private static Map getInvokerTasks(Project project) { + private static Map> getInvokerTasks(Project project) { MixinExtension mixin = LoomGradleExtension.get(project).getMixin(); - return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.JAVA) - .collect(Collectors.toMap(Map.Entry::getKey, entry -> Objects.requireNonNull((JavaCompile) entry.getValue()))); + return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.JAVA, JavaCompile.class) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override diff --git a/src/main/java/net/fabricmc/loom/build/mixin/KaptApInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/KaptApInvoker.java index f5ae7554..e6ebd259 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/KaptApInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/KaptApInvoker.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2020-2022 FabricMC + * Copyright (c) 2020-2025 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 @@ -36,6 +36,7 @@ import java.util.stream.Collectors; import kotlin.Unit; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.compile.JavaCompile; import org.jetbrains.kotlin.gradle.plugin.KaptExtension; @@ -66,35 +67,36 @@ public class KaptApInvoker extends AnnotationProcessorInvoker { kaptExtension.setIncludeCompileClasspath(false); } - private static Map getInvokerTasks(Project project) { + private static Map> getInvokerTasks(Project project) { MixinExtension mixin = LoomGradleExtension.get(project).getMixin(); - return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.JAVA) - .collect(Collectors.toMap(Map.Entry::getKey, entry -> Objects.requireNonNull((JavaCompile) entry.getValue()))); + return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.JAVA, JavaCompile.class) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override public void configureMixin() { super.configureMixin(); - for (Map.Entry entry : invokerTasks.entrySet()) { + for (Map.Entry> entry : invokerTasks.entrySet()) { // Kapt only allows specifying javac args to all annotation processors at once. So we need to specify some dummy // target location for the refmap and then move it to the correct place for each sourceset - JavaCompile task = entry.getValue(); - SourceSet sourceSet = entry.getKey(); - task.doLast(t -> { - try { - String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get(); - Path src = Paths.get(getRefmapDestination(task, refmapName)); - Path dest = Paths.get(task.getDestinationDirectory().get().getAsFile().toString(), refmapName); + entry.getValue().configure(task -> { + SourceSet sourceSet = entry.getKey(); + task.doLast(t -> { + try { + String refmapName = Objects.requireNonNull(MixinExtension.getMixinInformationContainer(sourceSet)).refmapNameProvider().get(); + Path src = Paths.get(getRefmapDestination(task, refmapName)); + Path dest = Paths.get(task.getDestinationDirectory().get().getAsFile().toString(), refmapName); - // Possible that no mixin annotations exist - if (Files.exists(src)) { - project.getLogger().info("Copying refmap from " + src + " to " + dest); - Files.move(src, dest); + // Possible that no mixin annotations exist + if (Files.exists(src)) { + project.getLogger().info("Copying refmap from " + src + " to " + dest); + Files.move(src, dest); + } + } catch (IOException e) { + project.getLogger().warn("Could not move refmap generated by kapt for task " + task, e); } - } catch (IOException e) { - project.getLogger().warn("Could not move refmap generated by kapt for task " + task, e); - } + }); }); } } diff --git a/src/main/java/net/fabricmc/loom/build/mixin/ScalaApInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/ScalaApInvoker.java index 81133208..b6fa1465 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/ScalaApInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/ScalaApInvoker.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2020 FabricMC + * Copyright (c) 2016-2025 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 @@ -26,12 +26,12 @@ package net.fabricmc.loom.build.mixin; import java.io.File; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import com.google.common.collect.ImmutableList; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.scala.ScalaCompile; import net.fabricmc.loom.LoomGradleExtension; @@ -47,10 +47,10 @@ public class ScalaApInvoker extends AnnotationProcessorInvoker { AnnotationProcessorInvoker.SCALA); } - private static Map getInvokerTasks(Project project) { + private static Map> getInvokerTasks(Project project) { MixinExtension mixin = LoomGradleExtension.get(project).getMixin(); - return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.SCALA) - .collect(Collectors.toMap(Map.Entry::getKey, entry -> Objects.requireNonNull((ScalaCompile) entry.getValue()))); + return mixin.getInvokerTasksStream(AnnotationProcessorInvoker.SCALA, ScalaCompile.class) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtension.java b/src/main/java/net/fabricmc/loom/extension/MixinExtension.java index 40795d54..e7c428fe 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtension.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtension.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2012 FabricMC + * Copyright (c) 2016-2025 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 @@ -36,6 +36,7 @@ import org.gradle.api.plugins.ExtraPropertiesExtension; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.util.PatternSet; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -81,7 +82,7 @@ public interface MixinExtension extends MixinExtensionAPI { Stream getApConfigurationsStream(Function getApConfigNameFunc); @NotNull - Stream> getInvokerTasksStream(String compileTaskLanguage); + Stream>> getInvokerTasksStream(String compileTaskLanguage, Class taskType); @NotNull @Input diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java index 5c31fce7..06281c67 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021-2022 FabricMC + * Copyright (c) 2021-2025 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 @@ -44,6 +44,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.util.PatternSet; import org.jetbrains.annotations.NotNull; @@ -57,6 +58,7 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx this.isDefault = true; this.defaultRefmapName = project.getObjects().property(String.class) .convention(project.provider(this::getDefaultMixinRefmapName)); + this.defaultRefmapName.finalizeValueOnRead(); } @Override @@ -100,11 +102,11 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx @Override @NotNull - public Stream> getInvokerTasksStream(String compileTaskLanguage) { + public Stream>> getInvokerTasksStream(String compileTaskLanguage, Class taskType) { return getMixinSourceSetsStream() .flatMap(sourceSet -> { try { - Task task = project.getTasks().getByName(sourceSet.getCompileTaskName(compileTaskLanguage)); + TaskProvider task = project.getTasks().named(sourceSet.getCompileTaskName(compileTaskLanguage), taskType); return Stream.of(new AbstractMap.SimpleEntry<>(sourceSet, task)); } catch (UnknownTaskException ignored) { return Stream.empty(); @@ -133,7 +135,7 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx if (sourceSet.getName().equals("main")) { add(sourceSet); } else { - add(sourceSet, sourceSet.getName() + "-" + getDefaultRefmapName().get()); + add(sourceSet, getDefaultRefmapName().map(defaultRefmapName -> "%s-%s".formatted(sourceSet.getName(), defaultRefmapName)), x -> { }); } }); } diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index a50d0a99..bf09aaed 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -107,7 +107,7 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { getOptimizeFabricModJson().convention(false).finalizeValueOnRead(); TaskProvider processIncludeJars = getProject().getTasks().named(Constants.Task.PROCESS_INCLUDE_JARS, NestableJarGenerationTask.class); - getNestedJars().from(getProject().fileTree(processIncludeJars.get().getOutputDirectory())); + getNestedJars().from(processIncludeJars.map(task -> getProject().fileTree(task.getOutputDirectory()))); getNestedJars().builtBy(processIncludeJars); getUseMixinAP().set(LoomGradleExtension.get(getProject()).getMixin().getUseLegacyMixinAp()); diff --git a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java index a7539afb..0da6a9ed 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java +++ b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java @@ -79,7 +79,7 @@ public abstract class RemapTaskConfiguration implements Runnable { }); Action remapJarTaskAction = task -> { - final AbstractArchiveTask jarTask = getTasks().named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class).get(); + final TaskProvider jarTask = getTasks().named(JavaPlugin.JAR_TASK_NAME, AbstractArchiveTask.class); // Basic task setup task.dependsOn(jarTask); @@ -89,7 +89,7 @@ public abstract class RemapTaskConfiguration implements Runnable { getArtifacts().add(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME, task); // Setup the input file and the nested deps - task.getInputFile().convention(jarTask.getArchiveFile()); + task.getInputFile().convention(jarTask.flatMap(AbstractArchiveTask::getArchiveFile)); task.dependsOn(getTasks().named(JavaPlugin.JAR_TASK_NAME)); task.getIncludesClientOnlyClasses().set(getProject().provider(extension::areEnvironmentSourceSetsSplit)); }; 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 13b197f7..f860b267 100644 --- a/src/main/java/net/fabricmc/loom/task/service/SourceRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/SourceRemapperService.java @@ -28,15 +28,19 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicInteger; import org.cadixdev.mercury.Mercury; import org.cadixdev.mercury.remapper.MercuryRemapper; +import org.gradle.api.JavaVersion; +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.gradle.api.tasks.compile.JavaCompile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,7 +73,7 @@ public final class SourceRemapperService extends Service { + Property releaseProperty = javaCompile.getOptions().getRelease(); + + if (!releaseProperty.isPresent()) { + return; + } + + int compileRelease = releaseProperty.get(); + release.set(Math.max(release.get(), compileRelease)); + }); + + final int i = release.get(); + + if (i < 0) { + // Unable to find the release used to compile with, default to the current version + return Integer.parseInt(JavaVersion.current().getMajorVersion()); + } + + return i; + } } diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index a574cf05..77e854e8 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -31,17 +31,13 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.cadixdev.lorenz.MappingSet; import org.cadixdev.mercury.Mercury; import org.cadixdev.mercury.remapper.MercuryRemapper; -import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.internal.project.ProjectInternal; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.internal.logging.progress.ProgressLogger; import org.gradle.internal.logging.progress.ProgressLoggerFactory; import org.slf4j.Logger; @@ -174,7 +170,8 @@ public class SourceRemapper { MappingSet mappings = lorenzMappingService.getMappings(); Mercury mercury = createMercuryWithClassPath(project, toNamed); - mercury.setSourceCompatibilityFromRelease(getJavaCompileRelease(project)); + // Always use the latest version + mercury.setSourceCompatibilityFromRelease(Integer.MAX_VALUE); for (File file : extension.getUnmappedModCollection()) { Path path = file.toPath(); @@ -206,30 +203,6 @@ public class SourceRemapper { return this.mercury; } - public static int getJavaCompileRelease(Project project) { - AtomicInteger release = new AtomicInteger(-1); - - project.getTasks().withType(JavaCompile.class, javaCompile -> { - Property releaseProperty = javaCompile.getOptions().getRelease(); - - if (!releaseProperty.isPresent()) { - return; - } - - int compileRelease = releaseProperty.get(); - release.set(Math.max(release.get(), compileRelease)); - }); - - final int i = release.get(); - - if (i < 0) { - // Unable to find the release used to compile with, default to the current version - return Integer.parseInt(JavaVersion.current().getMajorVersion()); - } - - return i; - } - public static void copyNonJavaFiles(Path from, Path to, Logger logger, Path source) throws IOException { Files.walk(from).forEach(path -> { Path targetPath = to.resolve(from.relativize(path).toString()); diff --git a/src/test/resources/projects/mixinApSimple/build.gradle b/src/test/resources/projects/mixinApSimple/build.gradle index 2e034dc6..9f565cdf 100644 --- a/src/test/resources/projects/mixinApSimple/build.gradle +++ b/src/test/resources/projects/mixinApSimple/build.gradle @@ -73,7 +73,10 @@ loom { mixin { useLegacyMixinAp = true - defaultRefmapName = "default-refmap0000.json" + // After evaluate block only to test for https://github.com/FabricMC/fabric-loom/issues/1249 + afterEvaluate { + defaultRefmapName = "default-refmap0000.json" + } add(sourceSets["main"], "main-refmap0000.json") add(sourceSets["mixin"]) From 74795b0250e2c2106174396218cb0a6f173142ac Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Mon, 27 Jan 2025 11:06:43 +0000 Subject: [PATCH 20/25] Add "productionRuntimeMods" configuration --- .../fabricmc/loom/configuration/LoomConfigurations.java | 2 ++ .../fabricmc/loom/task/prod/AbstractProductionRunTask.java | 2 ++ src/main/java/net/fabricmc/loom/util/Constants.java | 4 ++++ .../fabricmc/loom/test/integration/RunConfigTest.groovy | 7 +------ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java index 8922f3af..21a7ae7e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java @@ -158,6 +158,8 @@ public abstract class LoomConfigurations implements Runnable { extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.MINECRAFT_NATIVES); extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.MINECRAFT_CLIENT_RUNTIME_LIBRARIES); extendsFrom(Constants.Configurations.MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES, Constants.Configurations.LOADER_DEPENDENCIES); + + register(Constants.Configurations.PRODUCTION_RUNTIME_MODS, Role.RESOLVABLE); } private NamedDomainObjectProvider register(String name, Role role) { diff --git a/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java b/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java index 0661f969..55a28789 100644 --- a/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/prod/AbstractProductionRunTask.java @@ -132,6 +132,8 @@ public abstract sealed class AbstractProductionRunTask extends AbstractLoomTask if (!GradleUtils.getBooleanProperty(getProject(), Constants.Properties.DONT_REMAP)) { getMods().from(getProject().getTasks().named(RemapTaskConfiguration.REMAP_JAR_TASK_NAME)); } + + getMods().from(getProject().getConfigurations().named(Constants.Configurations.PRODUCTION_RUNTIME_MODS)); } @TaskAction diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index deceb58d..8a7a9c08 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -86,6 +86,10 @@ public class Constants { * The configuration that contains the Minecraft client and loader runtime libraries, as used by the production run tasks. */ public static final String MINECRAFT_TEST_CLIENT_RUNTIME_LIBRARIES = "minecraftTestClientRuntimeLibraries"; + /** + * Mods to be used by {@link net.fabricmc.loom.task.prod.AbstractProductionRunTask} tasks by default. + */ + public static final String PRODUCTION_RUNTIME_MODS = "productionRuntimeMods"; private Configurations() { } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy index f0f86bfb..7a8cacb7 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/RunConfigTest.groovy @@ -174,21 +174,16 @@ class RunConfigTest extends Specification implements GradleProjectTestTrait { def gradle = gradleProject(project: "minimalBase", version: version) gradle.buildGradle << ''' - configurations { - productionMods - } - dependencies { minecraft "com.mojang:minecraft:1.21.4" mappings "net.fabricmc:yarn:1.21.4+build.4:v2" modImplementation "net.fabricmc:fabric-loader:0.16.9" modImplementation "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4" - productionMods "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4" + productionRuntimeMods "net.fabricmc.fabric-api:fabric-api:0.114.0+1.21.4" } tasks.register("prodClient", net.fabricmc.loom.task.prod.ClientProductionRunTask) { - mods.from(configurations.productionMods) jvmArgs.add("-Dfabric.client.gametest") tracy { From 34edc76a50c284acc5112cdaa2e696c7f6277c80 Mon Sep 17 00:00:00 2001 From: modmuss Date: Sun, 9 Feb 2025 23:21:54 +0000 Subject: [PATCH 21/25] Improved class entry validation (#1260) * Improved class entry validation * Fixes and tests --- gradle/test.libs.versions.toml | 2 +- .../loom/decompilers/cache/ClassEntry.java | 41 ++++++++++-- .../loom/decompilers/cache/JarWalker.java | 11 ++-- .../test/unit/cache/ClassEntryTest.groovy | 63 +++++++++++++++++++ .../loom/test/unit/cache/JarWalkerTest.groovy | 6 +- 5 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index b609e5c2..6a854144 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -6,7 +6,7 @@ mockito = "5.14.2" java-debug = "0.52.0" mixin = "0.15.3+mixin.0.8.7" -gradle-nightly = "8.13-20250125001926+0000" +gradle-nightly = "8.14-20250208001853+0000" fabric-loader = "0.16.9" [libraries] diff --git a/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java b/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java index 666ae2c2..4fe53faa 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java +++ b/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2024 FabricMC + * Copyright (c) 2024-2025 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 @@ -45,6 +45,34 @@ import net.fabricmc.loom.util.Checksum; public record ClassEntry(String name, List innerClasses, List superClasses) { private static final Logger LOGGER = LoggerFactory.getLogger(ClassEntry.class); + public ClassEntry { + if (!name.endsWith(".class")) { + throw new IllegalArgumentException("Class name must end with '.class': " + name); + } + + if (!name.contains("/")) { + throw new IllegalArgumentException("Class name must be in a package: " + name); + } + + String className = name.replace(".class", ""); + + for (String innerClass : innerClasses) { + if (!innerClass.endsWith(".class")) { + throw new IllegalArgumentException("Inner class name must end with '.class': " + name); + } + + if (!innerClass.startsWith(className)) { + throw new IllegalArgumentException("Inner class (" + innerClass + ") does not have the parent class name as a prefix: " + name); + } + } + + for (String superClass : superClasses) { + if (!superClass.endsWith(".class")) { + throw new IllegalArgumentException("Super class name must end with '.class': " + superClass); + } + } + } + /** * Copy the class and its inner classes to the target root. * @param sourceRoot The root of the source jar @@ -55,13 +83,18 @@ public record ClassEntry(String name, List innerClasses, List su public void copyTo(Path sourceRoot, Path targetRoot) throws IOException { Path targetPath = targetRoot.resolve(name); Files.createDirectories(targetPath.getParent()); - Files.copy(sourceRoot.resolve(name), targetPath); + copy(sourceRoot.resolve(name), targetPath); for (String innerClass : innerClasses) { - Files.copy(sourceRoot.resolve(innerClass), targetRoot.resolve(innerClass)); + copy(sourceRoot.resolve(innerClass), targetRoot.resolve(innerClass)); } } + private void copy(Path source, Path target) throws IOException { + LOGGER.debug("Copying class entry `{}` from `{}` to `{}`", name, source, target); + Files.copy(source, target); + } + /** * Hash the class and its inner classes using sha256. * @param root The root of the jar @@ -95,7 +128,7 @@ public record ClassEntry(String name, List innerClasses, List su joiner.add(selfHash); for (String superClass : superClasses) { - final String superHash = hashes.get(superClass + ".class"); + final String superHash = hashes.get(superClass); if (superHash != null) { joiner.add(superHash); diff --git a/src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java b/src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java index a3931281..ad2e16b5 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java +++ b/src/main/java/net/fabricmc/loom/decompilers/cache/JarWalker.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2024 FabricMC + * Copyright (c) 2024-2025 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 @@ -194,11 +194,14 @@ public final class JarWalker { List parentClasses = new ArrayList<>(); String superName = reader.getSuperName(); - if (superName != null) { - parentClasses.add(superName); + if (superName != null && !superName.equals("java/lang/Object")) { + parentClasses.add(superName + ".class"); + } + + for (String iface : reader.getInterfaces()) { + parentClasses.add(iface + ".class"); } - Collections.addAll(parentClasses, reader.getInterfaces()); return Collections.unmodifiableList(parentClasses); } catch (IOException e) { throw new UncheckedIOException("Failed to read class file: " + classFile, e); diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy new file mode 100644 index 00000000..b766ecef --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/cache/ClassEntryTest.groovy @@ -0,0 +1,63 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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.cache + +import spock.lang.Specification + +import net.fabricmc.loom.decompilers.cache.ClassEntry + +class ClassEntryTest extends Specification { + def "valid class entry"() { + when: + def classEntry = new ClassEntry(name, innerClasses, superClasses) + then: + // Just make sure the constructor doesn't throw an exception + classEntry != null + where: + name | innerClasses | superClasses + "net/fabricmc/Test.class" | [] | [] + "net/fabricmc/Test.class" | [ + "net/fabricmc/Test\$Inner.class" + ] | ["java/lang/List.class"] + } + + def "invalid class entry"() { + when: + new ClassEntry(name, innerClasses, superClasses) + then: + thrown IllegalArgumentException + where: + name | innerClasses | superClasses + "net/fabricmc/Test" | [] | [] + "net/fabricmc/Test.class" | ["net/fabricmc/Test\$Inner"] | ["java/lang/List.class"] + "net/fabricmc/Test.class" | [ + "net/fabricmc/Test\$Inner.class" + ] | ["java/lang/List"] + "net/fabricmc/Test.class" | ["net/Test\$Inner.class"] | ["java/lang/List.class"] + "net/fabricmc/Test.class" | [ + "net/fabricmc/Bar\$Inner.class" + ] | [] + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/cache/JarWalkerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/cache/JarWalkerTest.groovy index ef6db7ec..edc829fc 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/cache/JarWalkerTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/cache/JarWalkerTest.groovy @@ -123,7 +123,7 @@ class JarWalkerTest extends Specification { classes.size() == 1 classes[0].name() == "net/fabricmc/Example.class" classes[0].innerClasses() == [] - classes[0].superClasses() == ["java/lang/Runnable"] + classes[0].superClasses() == ["java/lang/Runnable.class"] } def "inner classes"() { @@ -146,8 +146,8 @@ class JarWalkerTest extends Specification { "net/fabricmc/other/Test\$Inner.class" ] classes[0].superClasses() == [ - "java/lang/Runnable", - "net/fabricmc/other/Super" + "java/lang/Runnable.class", + "net/fabricmc/other/Super.class" ] } From e3cd4947bfde13ba7ad2abd0336d074c8a15ec69 Mon Sep 17 00:00:00 2001 From: modmuss Date: Sun, 9 Feb 2025 23:27:48 +0000 Subject: [PATCH 22/25] Remove loom bootstrap (#1261) * Remove bootstrap, its no longer required as Gradle has a nicer error message for outdated java. * No need to check idea or gradle version --- bootstrap/.gitignore | 7 -- bootstrap/build.gradle | 32 ------ .../loom/bootstrap/BootstrappedPlugin.java | 7 -- .../bootstrap/LoomGradlePluginBootstrap.java | 103 ------------------ bootstrap/test-project/build.gradle | 8 -- bootstrap/test-project/settings.gradle | 10 -- build.gradle | 29 +---- settings.gradle | 4 +- .../net/fabricmc/loom/LoomGradlePlugin.java | 11 +- 9 files changed, 8 insertions(+), 203 deletions(-) delete mode 100644 bootstrap/.gitignore delete mode 100644 bootstrap/build.gradle delete mode 100644 bootstrap/src/main/java/net/fabricmc/loom/bootstrap/BootstrappedPlugin.java delete mode 100644 bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java delete mode 100644 bootstrap/test-project/build.gradle delete mode 100644 bootstrap/test-project/settings.gradle diff --git a/bootstrap/.gitignore b/bootstrap/.gitignore deleted file mode 100644 index f8dc37c9..00000000 --- a/bootstrap/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Ignore everything -/* - -!/src -!/build.gradle -!/.gitignore -!/test-project \ No newline at end of file diff --git a/bootstrap/build.gradle b/bootstrap/build.gradle deleted file mode 100644 index 30b987a2..00000000 --- a/bootstrap/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -plugins { - id 'java' - id 'groovy' -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -tasks.withType(JavaCompile).configureEach { - it.options.encoding = "UTF-8" - it.options.release = 8 -} - -repositories { - mavenCentral() -} - -dependencies { - implementation gradleApi() - - testImplementation(gradleTestKit()) - testImplementation('org.spockframework:spock-core:2.3-groovy-3.0') { - exclude module: 'groovy-all' - } -} - -test { - maxHeapSize = "4096m" - useJUnitPlatform() -} \ No newline at end of file diff --git a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/BootstrappedPlugin.java b/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/BootstrappedPlugin.java deleted file mode 100644 index c64246ef..00000000 --- a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/BootstrappedPlugin.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.fabricmc.loom.bootstrap; - -import org.gradle.api.plugins.PluginAware; - -public interface BootstrappedPlugin { - void apply(PluginAware project); -} diff --git a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java b/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java deleted file mode 100644 index 35113243..00000000 --- a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java +++ /dev/null @@ -1,103 +0,0 @@ -package net.fabricmc.loom.bootstrap; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -import org.gradle.api.JavaVersion; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.configuration.BuildFeatures; -import org.gradle.api.plugins.PluginAware; -import org.gradle.util.GradleVersion; - -/** - * This bootstrap is compiled against a minimal gradle API and java 8, this allows us to show a nice error to users who run on unsupported configurations. - */ -@SuppressWarnings("unused") -public abstract class LoomGradlePluginBootstrap implements Plugin { - private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.12"; - private static final int MIN_SUPPORTED_MAJOR_JAVA_VERSION = 17; - private static final int MIN_SUPPORTED_MAJOR_IDEA_VERSION = 2022; - - private static final String PLUGIN_CLASS_NAME = "net.fabricmc.loom.LoomGradlePlugin"; - private static final String IDEA_VERSION_PROP_KEY = "idea.version"; - - @Inject - protected abstract BuildFeatures getBuildFeatures(); - - @Override - public void apply(PluginAware pluginAware) { - if (pluginAware instanceof Project) { - Project project = (Project) pluginAware; - - if (getBuildFeatures().getIsolatedProjects().getActive().get() || project.findProperty("fabric.loom.skip-env-validation") == null) { - validateEnvironment(); - } else { - project.getLogger().lifecycle("Loom environment validation disabled. Please re-enable before reporting any issues."); - } - } - - getActivePlugin().apply(pluginAware); - } - - private void validateEnvironment() { - List errors = new ArrayList<>(); - - if (!isValidGradleRuntime()) { - errors.add(String.format("You are using an outdated version of Gradle (%s). Gradle %s or higher is required.", GradleVersion.current().getVersion(), MIN_SUPPORTED_GRADLE_VERSION)); - } - - if (!isValidJavaRuntime()) { - errors.add(String.format("You are using an outdated version of Java (%s). Java %d or higher is required.", JavaVersion.current().getMajorVersion(), MIN_SUPPORTED_MAJOR_JAVA_VERSION)); - - if (Boolean.getBoolean("idea.active")) { - // Idea specific error - errors.add("You can change the Java version in the Gradle settings dialog."); - } else { - String javaHome = System.getenv("JAVA_HOME"); - - if (javaHome != null) { - errors.add(String.format("The JAVA_HOME environment variable is currently set to (%s).", javaHome)); - } - } - } - - if (!isValidIdeaRuntime()) { - errors.add(String.format("You are using an outdated version of intellij idea (%s). Intellij idea %d or higher is required.", System.getProperty(IDEA_VERSION_PROP_KEY), MIN_SUPPORTED_MAJOR_IDEA_VERSION)); - } - - if (!errors.isEmpty()) { - throw new UnsupportedOperationException(String.join("\n", errors)); - } - } - - private static boolean isValidJavaRuntime() { - // Note use compareTo to ensure compatibility with gradle < 6.0 - return JavaVersion.current().compareTo(JavaVersion.toVersion(MIN_SUPPORTED_MAJOR_JAVA_VERSION)) >= 0; - } - - private static boolean isValidGradleRuntime() { - return GradleVersion.current().compareTo(GradleVersion.version(MIN_SUPPORTED_GRADLE_VERSION)) >= 0; - } - - private static boolean isValidIdeaRuntime() { - String version = System.getProperty(IDEA_VERSION_PROP_KEY); - - if (version == null) { - return true; - } - - int ideaYear = Integer.parseInt(version.substring(0, version.indexOf("."))); - return ideaYear >= MIN_SUPPORTED_MAJOR_IDEA_VERSION; - } - - BootstrappedPlugin getActivePlugin() { - try { - return (BootstrappedPlugin) Class.forName(PLUGIN_CLASS_NAME).getConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException("Failed to bootstrap loom", e); - } - } -} diff --git a/bootstrap/test-project/build.gradle b/bootstrap/test-project/build.gradle deleted file mode 100644 index 56191542..00000000 --- a/bootstrap/test-project/build.gradle +++ /dev/null @@ -1,8 +0,0 @@ -plugins { - id 'fabric-loom' version '0.13.local' -} - -dependencies { - minecraft "com.mojang:minecraft:1.16.5" - mappings loom.officialMojangMappings() -} \ No newline at end of file diff --git a/bootstrap/test-project/settings.gradle b/bootstrap/test-project/settings.gradle deleted file mode 100644 index 37a1b9e0..00000000 --- a/bootstrap/test-project/settings.gradle +++ /dev/null @@ -1,10 +0,0 @@ -pluginManagement { - repositories { - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - gradlePluginPortal() - mavenLocal() - } -} diff --git a/build.gradle b/build.gradle index db58217e..1c1955be 100644 --- a/build.gradle +++ b/build.gradle @@ -54,15 +54,6 @@ repositories { mavenCentral() } -configurations { - bootstrap { - transitive = false - } - compileClasspath.extendsFrom bootstrap - runtimeClasspath.extendsFrom bootstrap - testRuntimeClasspath.extendsFrom bootstrap -} - configurations.configureEach { resolutionStrategy { failOnNonReproducibleResolution() @@ -101,8 +92,6 @@ sourceSets { dependencies { implementation gradleApi() - bootstrap project(":bootstrap") - // libraries implementation libs.commons.io implementation libs.gson @@ -180,7 +169,6 @@ jar { attributes 'Implementation-Version': project.version } - from configurations.bootstrap.collect { it.isDirectory() ? it : zipTree(it) } from sourceSets.commonDecompiler.output.classesDirs from sourceSets.cfr.output.classesDirs from sourceSets.fernflower.output.classesDirs @@ -249,7 +237,7 @@ gradlePlugin { plugins { fabricLoom { id = 'fabric-loom' - implementationClass = 'net.fabricmc.loom.bootstrap.LoomGradlePluginBootstrap' + implementationClass = 'net.fabricmc.loom.LoomGradlePlugin' } } } @@ -341,21 +329,6 @@ publishing { } } -// Need to tweak this file to pretend we are compatible with j8 so the bootstrap will run. -tasks.withType(GenerateModuleMetadata).configureEach { - doLast { - def file = outputFile.get().asFile - - def metadata = new groovy.json.JsonSlurper().parseText(file.text) - - metadata.variants.each { - it.attributes["org.gradle.jvm.version"] = 8 - } - - file.text = groovy.json.JsonOutput.toJson(metadata) - } -} - // A task to output a json file with a list of all the test to run tasks.register('writeActionsTestMatrix') { doLast { diff --git a/settings.gradle b/settings.gradle index 039a49da..6b14589d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,6 +9,4 @@ dependencyResolutionManagement { from(files("gradle/runtime.libs.versions.toml")) } } -} - -include "bootstrap" \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index 144c548f..b3cdf145 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2023 FabricMC + * Copyright (c) 2016-2025 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 @@ -30,16 +30,16 @@ import java.util.Objects; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.plugins.PluginAware; import net.fabricmc.loom.api.LoomGradleExtensionAPI; import net.fabricmc.loom.api.fabricapi.FabricApiExtension; -import net.fabricmc.loom.bootstrap.BootstrappedPlugin; import net.fabricmc.loom.configuration.CompileConfiguration; -import net.fabricmc.loom.configuration.fabricapi.FabricApiExtensionImpl; import net.fabricmc.loom.configuration.LoomConfigurations; import net.fabricmc.loom.configuration.MavenPublication; +import net.fabricmc.loom.configuration.fabricapi.FabricApiExtensionImpl; import net.fabricmc.loom.configuration.ide.idea.IdeaConfiguration; import net.fabricmc.loom.configuration.sandbox.SandboxConfiguration; import net.fabricmc.loom.decompilers.DecompilerConfiguration; @@ -49,7 +49,7 @@ import net.fabricmc.loom.task.LoomTasks; import net.fabricmc.loom.task.RemapTaskConfiguration; import net.fabricmc.loom.util.LibraryLocationLogger; -public class LoomGradlePlugin implements BootstrappedPlugin { +public class LoomGradlePlugin implements Plugin { public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); public static final String LOOM_VERSION = Objects.requireNonNullElse(LoomGradlePlugin.class.getPackage().getImplementationVersion(), "0.0.0+unknown"); @@ -76,8 +76,9 @@ public class LoomGradlePlugin implements BootstrappedPlugin { } } - public void apply(Project project) { + private void apply(Project project) { project.getLogger().lifecycle("Fabric Loom: " + LOOM_VERSION); + LibraryLocationLogger.logLibraryVersions(); // Apply default plugins From 2cd695b79927dd2eb22157e383e8d35d48c83c42 Mon Sep 17 00:00:00 2001 From: modmuss Date: Mon, 10 Feb 2025 08:56:18 +0000 Subject: [PATCH 23/25] Add general purpose download task (#1262) * Add general purpose download task * Use duration + add basic max age test * Enable default caching --- .../net/fabricmc/loom/task/DownloadTask.java | 145 ++++++++++++++++++ .../test/integration/DownloadTaskTest.groovy | 130 ++++++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 src/main/java/net/fabricmc/loom/task/DownloadTask.java create mode 100644 src/test/groovy/net/fabricmc/loom/test/integration/DownloadTaskTest.groovy diff --git a/src/main/java/net/fabricmc/loom/task/DownloadTask.java b/src/main/java/net/fabricmc/loom/task/DownloadTask.java new file mode 100644 index 00000000..beddbe34 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/DownloadTask.java @@ -0,0 +1,145 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 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.net.URISyntaxException; +import java.time.Duration; + +import javax.inject.Inject; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +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 org.jetbrains.annotations.ApiStatus; + +import net.fabricmc.loom.util.ExceptionUtil; +import net.fabricmc.loom.util.download.Download; +import net.fabricmc.loom.util.download.DownloadBuilder; +import net.fabricmc.loom.util.download.DownloadException; + +/** + * A general purpose task for downloading files from a URL, using the loom {@link Download} utility. + */ +public abstract class DownloadTask extends DefaultTask { + /** + * The URL to download the file from. + */ + @Input + public abstract Property getUrl(); + + /** + * The expected SHA-1 hash of the downloaded file. + */ + @Optional + @Input + public abstract Property getSha1(); + + /** + * The maximum age of the downloaded file in days. When not provided the downloaded file will never be considered stale. + */ + @Optional + @Input + public abstract Property getMaxAge(); + + /** + * The file to download to. + */ + @OutputFile + public abstract RegularFileProperty getOutput(); + + // Internal stuff: + + @ApiStatus.Internal + @Input + protected abstract Property getIsOffline(); + + @Inject + protected abstract WorkerExecutor getWorkerExecutor(); + + @Inject + public DownloadTask() { + getIsOffline().set(getProject().getGradle().getStartParameter().isOffline()); + } + + @TaskAction + public void run() { + final WorkQueue workQueue = getWorkerExecutor().noIsolation(); + + workQueue.submit(DownloadAction.class, params -> { + params.getUrl().set(getUrl()); + params.getSha1().set(getSha1()); + params.getMaxAge().set(getMaxAge()); + params.getOutputFile().set(getOutput()); + params.getIsOffline().set(getIsOffline()); + }); + } + + public interface DownloadWorkParameters extends WorkParameters { + Property getUrl(); + Property getSha1(); + Property getMaxAge(); + RegularFileProperty getOutputFile(); + Property getIsOffline(); + } + + public abstract static class DownloadAction implements WorkAction { + @Override + public void execute() { + DownloadBuilder builder; + + try { + builder = Download.create(getParameters().getUrl().get()).defaultCache(); + } catch (URISyntaxException e) { + throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Invalid URL", e); + } + + if (getParameters().getMaxAge().isPresent()) { + builder.maxAge(getParameters().getMaxAge().get()); + } + + if (getParameters().getSha1().isPresent()) { + builder.sha1(getParameters().getSha1().get()); + } + + if (getParameters().getIsOffline().get()) { + builder.offline(); + } + + try { + builder.downloadPath(getParameters().getOutputFile().get().getAsFile().toPath()); + } catch (DownloadException e) { + throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to download file", e); + } + } + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/DownloadTaskTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/DownloadTaskTest.groovy new file mode 100644 index 00000000..59e358b9 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DownloadTaskTest.groovy @@ -0,0 +1,130 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.integration + +import spock.lang.Unroll + +import net.fabricmc.loom.test.unit.download.DownloadTest +import net.fabricmc.loom.test.util.GradleProjectTestTrait + +import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class DownloadTaskTest extends DownloadTest implements GradleProjectTestTrait { + @Unroll + def "download (gradle #version)"() { + setup: + server.get("/simpleFile") { + it.result("Hello World") + } + + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildGradle << """ + dependencies { + minecraft "com.mojang:minecraft:1.21.4" + mappings "net.fabricmc:yarn:1.21.4+build.8:v2" + } + + tasks.register("download", net.fabricmc.loom.task.DownloadTask) { + url = "${PATH}/simpleFile" + output = file("out.txt") + } + """ + when: + def result = gradle.run(task: "download") + def output = new File(gradle.projectDir, "out.txt") + + then: + result.task(":download").outcome == SUCCESS + output.text == "Hello World" + + where: + version << STANDARD_TEST_VERSIONS + } + + @Unroll + def "download sha1 (gradle #version)"() { + setup: + server.get("/simpleFile") { + it.result("Hello World") + } + + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildGradle << """ + dependencies { + minecraft "com.mojang:minecraft:1.21.4" + mappings "net.fabricmc:yarn:1.21.4+build.8:v2" + } + + tasks.register("download", net.fabricmc.loom.task.DownloadTask) { + url = "${PATH}/simpleFile" + sha1 = "0a4d55a8d778e5022fab701977c5d840bbc486d0" + output = file("out.txt") + } + """ + when: + def result = gradle.run(task: "download") + def output = new File(gradle.projectDir, "out.txt") + + then: + result.task(":download").outcome == SUCCESS + output.text == "Hello World" + + where: + version << STANDARD_TEST_VERSIONS + } + + @Unroll + def "download max age (gradle #version)"() { + setup: + server.get("/simpleFile") { + it.result("Hello World") + } + + def gradle = gradleProject(project: "minimalBase", version: version) + gradle.buildGradle << """ + dependencies { + minecraft "com.mojang:minecraft:1.21.4" + mappings "net.fabricmc:yarn:1.21.4+build.8:v2" + } + + tasks.register("download", net.fabricmc.loom.task.DownloadTask) { + url = "${PATH}/simpleFile" + maxAge = Duration.ofDays(1) + output = file("out.txt") + } + """ + when: + def result = gradle.run(task: "download") + def output = new File(gradle.projectDir, "out.txt") + + then: + result.task(":download").outcome == SUCCESS + output.text == "Hello World" + + where: + version << STANDARD_TEST_VERSIONS + } +} From a21db850ea12d246f7bdd7e3406e0ee1fd666aad Mon Sep 17 00:00:00 2001 From: modmuss Date: Tue, 25 Feb 2025 14:48:33 +0000 Subject: [PATCH 24/25] Vineflower 0.11.0 (#1267) --- gradle/runtime.libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml index 0e3f7cf1..ce236d5d 100644 --- a/gradle/runtime.libs.versions.toml +++ b/gradle/runtime.libs.versions.toml @@ -2,7 +2,7 @@ # Decompilers fernflower = "2.0.0" cfr = "0.2.2" -vineflower = "1.10.1" +vineflower = "1.11.0" # Runtime depedencies mixin-compile-extensions = "0.6.0" From 8b0e7185738922c31f55629083c388c41dc1f16b Mon Sep 17 00:00:00 2001 From: modmuss Date: Fri, 28 Feb 2025 09:50:11 +0000 Subject: [PATCH 25/25] Fix client test resources dir (#1269) --- .../fabricmc/loom/configuration/fabricapi/FabricApiTesting.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java index 23084076..8876e045 100644 --- a/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java +++ b/src/main/java/net/fabricmc/loom/configuration/fabricapi/FabricApiTesting.java @@ -103,7 +103,7 @@ public abstract class FabricApiTesting extends FabricApiAbstractSourceSet { if (settings.getEnableClientGameTests().get()) { // Not ideal as there may be multiple resources directories, if this isnt correct the mod will need to override this. - final File resourcesDir = testSourceSet.getResources().getFiles().stream().findAny().orElse(null); + final File resourcesDir = testSourceSet.getResources().getSrcDirs().stream().findFirst().orElse(null); RunConfigSettings clientGameTest = extension.getRunConfigs().create("clientGameTest", run -> { run.inherit(extension.getRunConfigs().getByName("client"));