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 f0c999b9..0b852973 100644 --- a/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java +++ b/bootstrap/src/main/java/net/fabricmc/loom/bootstrap/LoomGradlePluginBootstrap.java @@ -3,9 +3,12 @@ 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; @@ -13,20 +16,23 @@ 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 class LoomGradlePluginBootstrap implements Plugin { - private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.10"; +public abstract class LoomGradlePluginBootstrap implements Plugin { + private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.11"; 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 (project.findProperty("fabric.loom.skip-env-validation") == null) { + 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."); diff --git a/build.gradle b/build.gradle index 495d3061..03de0ef0 100644 --- a/build.gradle +++ b/build.gradle @@ -7,37 +7,11 @@ plugins { id 'groovy' id 'checkstyle' id 'codenarc' - alias(libs.plugins.kotlin) apply false // Delay this so we can perform magic 🪄 first. + alias(libs.plugins.kotlin) alias(libs.plugins.spotless) alias(libs.plugins.retry) } -/** - * Haha this is fun :) The Kotlin gradle plugin triggers deprecation warnings for custom configurations (https://youtrack.jetbrains.com/issue/KT-60879) - * We need to make DefaultConfiguration.isSpecialCaseOfChangingUsage think that our configurstion is a special case and not deprecated. - * We do this by setting DefaultConfiguration.roleAtCreation to LEGACY, thus isInLegacyRole will now return true. - * - * Yeah I know we can just ignore the deprecation warning, but doing so wouldn't alert us to issues when testing against pre-release Gradle versions. Also this is more fun :) - */ -def brokenConfigurations = [ - "commonDecompilerRuntimeClasspath", - "fernflowerRuntimeClasspath", - "cfrRuntimeClasspath", - "vineflowerRuntimeClasspath" -] - -configurations.configureEach { - if (brokenConfigurations.contains(it.name)) { - // For some reason Gradle stops us from using Groovy magic to do this, so lets do it the boring way. - def field = org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.class.getDeclaredField("roleAtCreation") - field.setAccessible(true) - field.set(it, ConfigurationRoles.LEGACY) - } -} - -// Ensure we apply the Kotlin plugin after, to allow for the above configuration to take place first -apply plugin: libs.plugins.kotlin.get().pluginId - tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" } @@ -49,7 +23,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { } group = "dev.architectury" -def baseVersion = '1.8' +def baseVersion = '1.9' def ENV = System.getenv() def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999" diff --git a/gradle.properties b/gradle.properties index 64ab0fde..960d6941 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,6 @@ name = architectury-loom description = A Gradle plugin for Fabric, Forge and Quilt modding. url = https://github.com/architectury/architectury-loom -kotlin.stdlib.default.dependency = false \ No newline at end of file +kotlin.stdlib.default.dependency = false +# Suppress a deprecation warning within the Kotlin Gradle plugin +kotlin.mpp.keepMppDependenciesIntactInPoms = true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f0613107..05ac7bb5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.9.24" +kotlin = "2.0.20" asm = "9.7.1" commons-io = "2.15.1" gson = "2.10.1" @@ -11,7 +11,6 @@ access-widener = "2.1.0" mapping-io = "0.6.1" lorenz-tiny = "4.0.2" mercury = "0.1.4.17" -kotlinx-metadata = "0.9.0" loom-native = "0.2.0" # Plugins @@ -52,7 +51,7 @@ fabric-loom-nativelib = { module = "net.fabricmc:fabric-loom-native", version.re # Misc kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } -kotlin-metadata = { module = "org.jetbrains.kotlinx:kotlinx-metadata-jvm", version.ref = "kotlinx-metadata" } +kotlin-metadata = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } # Forge support forge-installer-tools = { module = "net.minecraftforge:installertools", version.ref = "forge-installer-tools" } diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index 2b091d91..4bc6312a 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -1,13 +1,13 @@ [versions] spock = "2.3-groovy-3.0" -junit = "5.11.1" +junit = "5.11.3" javalin = "6.3.0" -mockito = "5.13.0" +mockito = "5.14.2" java-debug = "0.52.0" mixin = "0.15.3+mixin.0.8.7" -gradle-nightly = "8.12-20241009055624+0000" -fabric-loader = "0.16.5" +gradle-nightly = "8.12-20241110002642+0000" +fabric-loader = "0.16.9" fabric-installer = "1.0.1" [libraries] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 66cd5a0e..7cf748e7 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.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index 0344bbcf..9854547c 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -153,6 +153,8 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { boolean isConfigurationCacheActive(); + boolean isProjectIsolationActive(); + // =================== // Architectury Loom // =================== diff --git a/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java b/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java index 2ec61a28..845106f3 100644 --- a/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java +++ b/src/main/java/net/fabricmc/loom/build/nesting/JarNester.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.Collection; +import java.util.Comparator; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,8 +55,11 @@ public class JarNester { Preconditions.checkArgument(FabricModJsonFactory.isNestableModJar(modJar, platform), "Cannot nest jars into none mod jar " + modJar.getName()); + // Ensure deterministic ordering of entries in fabric.mod.json + Collection sortedJars = jars.stream().sorted(Comparator.comparing(File::getName)).toList(); + try { - ZipUtils.add(modJar.toPath(), jars.stream().map(file -> { + ZipUtils.add(modJar.toPath(), sortedJars.stream().map(file -> { try { return new Pair<>("META-INF/jars/" + file.getName(), Files.readAllBytes(file.toPath())); } catch (IOException e) { @@ -75,7 +79,7 @@ public class JarNester { nestedJars = new JsonArray(); } - for (File file : jars) { + for (File file : sortedJars) { String nestedJarPath = "META-INF/jars/" + file.getName(); Preconditions.checkArgument(FabricModJsonFactory.isNestableModJar(file, platform), "Cannot nest none mod jar: " + file.getName()); diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index af61526f..a7f23cde 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.configuration; import static net.fabricmc.loom.util.Constants.Configurations; +import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; @@ -34,6 +35,7 @@ import java.nio.file.Path; import java.time.Duration; import java.util.Optional; import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -49,6 +51,7 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.javadoc.Javadoc; +import org.gradle.api.tasks.testing.Test; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI; @@ -89,6 +92,7 @@ import net.fabricmc.loom.util.ExceptionUtil; import net.fabricmc.loom.util.ProcessUtil; import net.fabricmc.loom.util.gradle.GradleUtils; import net.fabricmc.loom.util.gradle.SourceSetHelper; +import net.fabricmc.loom.util.gradle.daemon.DaemonUtils; import net.fabricmc.loom.util.service.ScopedServiceFactory; import net.fabricmc.loom.util.service.ServiceFactory; @@ -129,7 +133,7 @@ public abstract class CompileConfiguration implements Runnable { extension.setDependencyManager(dependencyManager); dependencyManager.handleDependencies(getProject(), serviceFactory); } catch (Exception e) { - ExceptionUtil.processException(e, getProject()); + ExceptionUtil.processException(e, DaemonUtils.Context.fromProject(getProject())); disownLock(); throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e); } @@ -144,6 +148,7 @@ public abstract class CompileConfiguration implements Runnable { } configureDecompileTasks(configContext); + configureTestTask(); if (extension.isForgeLike()) { if (extension.isDataGenEnabled()) { @@ -335,6 +340,26 @@ public abstract class CompileConfiguration implements Runnable { .afterEvaluation(); } + private void configureTestTask() { + final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + + if (extension.getMods().isEmpty()) { + return; + } + + getProject().getTasks().named(JavaPlugin.TEST_TASK_NAME, Test.class, test -> { + String classPathGroups = extension.getMods().stream() + .map(modSettings -> + SourceSetHelper.getClasspath(modSettings, getProject()).stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)) + ) + .collect(Collectors.joining(File.pathSeparator+File.pathSeparator));; + + test.systemProperty("fabric.classPathGroups", classPathGroups); + }); + } + private LockFile getLockFile() { final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); final Path cacheDirectory = extension.getFiles().getUserCache().toPath(); diff --git a/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java b/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java index 67d97342..26d6b271 100644 --- a/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java +++ b/src/main/java/net/fabricmc/loom/configuration/FabricApiExtension.java @@ -44,17 +44,16 @@ 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.Delete; 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.gradle.language.base.plugins.LifecycleBasePlugin; 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; @@ -112,6 +111,7 @@ public abstract class FabricApiExtension { settings.getCreateSourceSet().convention(false); settings.getStrictValidation().convention(false); settings.getAddToResources().convention(true); + settings.getClient().convention(false); action.execute(settings); @@ -133,33 +133,23 @@ public abstract class FabricApiExtension { jar.exclude(".cache/**"); }); - taskContainer.getByName(LifecycleBasePlugin.CLEAN_TASK_NAME, task -> { - Delete clean = (Delete) task; - clean.delete(outputDirectory); - }); - 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 sourceset. + // Create the new datagen sourceset, depend on the main or client sourceset. SourceSet dataGenSourceSet = sourceSets.create(DATAGEN_SOURCESET_NAME, sourceSet -> { - sourceSet.setCompileClasspath( - sourceSet.getCompileClasspath() - .plus(mainSourceSet.getOutput()) - ); + dependsOn(sourceSet, mainSourceSet); - sourceSet.setRuntimeClasspath( - sourceSet.getRuntimeClasspath() - .plus(mainSourceSet.getOutput()) - ); - - extendsFrom(getProject(), sourceSet.getCompileClasspathConfigurationName(), mainSourceSet.getCompileClasspathConfigurationName()); - extendsFrom(getProject(), sourceSet.getRuntimeClasspathConfigurationName(), mainSourceSet.getRuntimeClasspathConfigurationName()); + 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(dataGenSourceSet); + 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()"); @@ -181,7 +171,7 @@ public abstract class FabricApiExtension { if (settings.getCreateRunConfiguration().get()) { extension.getRunConfigs().create("datagen", run -> { - run.inherit(extension.getRunConfigs().getByName("server")); + run.inherit(extension.getRunConfigs().getByName(settings.getClient().get() ? "client" : "server")); run.setConfigName("Data Generation"); run.property("fabric-api.datagen"); @@ -200,6 +190,11 @@ public abstract class FabricApiExtension { 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); + }); } } @@ -235,6 +230,11 @@ public abstract class FabricApiExtension { * 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) { @@ -326,4 +326,19 @@ public abstract class FabricApiExtension { 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/processors/SpecContextImpl.java b/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java index 18826346..d9d2aac8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/SpecContextImpl.java @@ -84,7 +84,8 @@ public record SpecContextImpl(List modDependencies, List { @@ -97,8 +98,8 @@ public record SpecContextImpl(List modDependencies, List getDependentProjects(Project project) { - final Stream runtimeProjects = getLoomProjectDependencies(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); - final Stream compileProjects = getLoomProjectDependencies(project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)); + final Stream runtimeProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + final Stream compileProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)); return Stream.concat(runtimeProjects, compileProjects) .distinct(); @@ -154,19 +155,19 @@ public record SpecContextImpl(List modDependencies, List getCompileRuntimeProjectDependencies(Project project) { - final Stream runtimeProjects = getLoomProjectDependencies(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); - final List compileProjects = getLoomProjectDependencies(project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)).toList(); + final Stream runtimeProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); + final List compileProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)).toList(); return runtimeProjects .filter(compileProjects::contains); // Use the intersection of the two configurations. } // Returns a list of Loom Projects found in the provided Configuration - private static Stream getLoomProjectDependencies(Configuration configuration) { + private static Stream getLoomProjectDependencies(Project project, Configuration configuration) { return configuration.getAllDependencies() .withType(ProjectDependency.class) .stream() - .map(GradleUtils::getDependencyProject) + .map((d) -> project.project(d.getPath())) .filter(GradleUtils::isLoomProject); } 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 369083ae..de9a80fd 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 @@ -45,6 +45,7 @@ import net.fabricmc.loom.configuration.providers.minecraft.library.MinecraftLibr import net.fabricmc.loom.configuration.providers.minecraft.library.processors.RuntimeLog4jLibraryProcessor; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Platform; +import net.fabricmc.loom.util.gradle.GradleUtils; public class MinecraftLibraryProvider { private static final Platform platform = Platform.CURRENT; @@ -124,7 +125,7 @@ public class MinecraftLibraryProvider { } private JavaVersion getTargetRuntimeJavaVersion() { - final Object property = project.findProperty(Constants.Properties.RUNTIME_JAVA_COMPATIBILITY_VERSION); + final Object property = GradleUtils.getProperty(project, Constants.Properties.RUNTIME_JAVA_COMPATIBILITY_VERSION); if (property != null) { // This is very much a last ditch effort to allow users to set the runtime java version diff --git a/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxConfiguration.java index a07ee658..0f5bc1f8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/sandbox/SandboxConfiguration.java @@ -59,6 +59,13 @@ public abstract class SandboxConfiguration implements Runnable { @Override public void run() { + LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + + if (extension.isProjectIsolationActive()) { + LOGGER.debug("Skipping sandbox configuration as project isolation is enabled."); + return; + } + if (getProject().findProperty(Constants.Properties.SANDBOX) == null) { LOGGER.debug("No fabric sandbox property set"); return; diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index 6ad537e6..8131e701 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -342,7 +342,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA @Override public String getModVersion() { try { - final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(SourceSetHelper.getMainSourceSet(getProject())); + final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), SourceSetHelper.getMainSourceSet(getProject())); if (fabricModJson == null) { throw new RuntimeException("Could not find a fabric.mod.json file in the main sourceset"); diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java index db9e548c..ed76464e 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java @@ -131,6 +131,10 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl if (refreshDeps) { project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower."); } + + if (isolatedProjectsActive) { + project.getLogger().lifecycle("Isolated projects is enabled, Loom support is highly experimental, not all features will be enabled."); + } } @Override @@ -339,6 +343,11 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl return configurationCacheActive; } + @Override + public boolean isProjectIsolationActive() { + return isolatedProjectsActive; + } + @Override public ForgeExtensionAPI getForge() { ModPlatform.assertPlatform(this, ModPlatform.FORGE); diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java index 74100bf5..6276a059 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java @@ -126,6 +126,11 @@ public abstract class AbstractRemapJarTask extends Jar { usesService(jarManifestServiceProvider); } + @Override + protected void copy() { + // Skip the default copy behaviour of AbstractCopyTask. + } + public final

void submitWork(Class> workAction, Action

action) { final WorkQueue workQueue = getWorkerExecutor().noIsolation(); diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java index 67191ff5..8a0a4b72 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRunTask.java @@ -31,6 +31,7 @@ import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.function.Function; @@ -43,7 +44,6 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; -import org.gradle.api.services.ServiceReference; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; @@ -52,9 +52,9 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.gradle.SyncTaskBuildService; public abstract class AbstractRunTask extends JavaExec { private static final CharsetEncoder ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder(); @@ -70,15 +70,14 @@ public abstract class AbstractRunTask extends JavaExec { protected abstract Property getUseArgFile(); @Input protected abstract Property getProjectDir(); + @Input + // We use a string here, as it's technically an output, but we don't want to cache runs of this task by default. + protected abstract Property getArgFilePath(); // We control the classpath, as we use a ArgFile to pass it over the command line: https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#commandlineargfile @InputFiles protected abstract ConfigurableFileCollection getInternalClasspath(); - // Prevent Gradle from running two run tasks in parallel - @ServiceReference(SyncTaskBuildService.NAME) - abstract Property getSyncTask(); - public AbstractRunTask(Function configProvider) { super(); setGroup(Constants.TaskGroup.FABRIC); @@ -100,6 +99,10 @@ public abstract class AbstractRunTask extends JavaExec { getInternalJvmArgs().set(config.map(runConfig -> runConfig.vmArgs)); getUseArgFile().set(getProject().provider(this::canUseArgFile)); getProjectDir().set(getProject().getProjectDir().getAbsolutePath()); + + File buildCache = LoomGradleExtension.get(getProject()).getFiles().getProjectBuildCache(); + File argFile = new File(buildCache, "argFiles/" + getName()); + getArgFilePath().set(argFile.getAbsolutePath()); } private boolean canUseArgFile() { @@ -154,7 +157,8 @@ public abstract class AbstractRunTask extends JavaExec { .collect(Collectors.joining(File.pathSeparator)); try { - final Path argsFile = Files.createTempFile("loom-classpath", ".args"); + final Path argsFile = Paths.get(getArgFilePath().get()); + Files.createDirectories(argsFile.getParent()); Files.writeString(argsFile, content, StandardCharsets.UTF_8); args.add("@" + argsFile.toAbsolutePath()); } catch (IOException e) { diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 65bb5234..6af0566f 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -97,10 +97,12 @@ import net.fabricmc.loom.util.ExceptionUtil; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.IOStringConsumer; import net.fabricmc.loom.util.Platform; +import net.fabricmc.loom.util.gradle.GradleUtils; import net.fabricmc.loom.util.gradle.SyncTaskBuildService; import net.fabricmc.loom.util.gradle.ThreadedProgressLoggerConsumer; import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger; import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper; +import net.fabricmc.loom.util.gradle.daemon.DaemonUtils; import net.fabricmc.loom.util.ipc.IPCClient; import net.fabricmc.loom.util.ipc.IPCServer; import net.fabricmc.loom.util.service.ScopedServiceFactory; @@ -178,6 +180,14 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @Internal protected abstract RegularFileProperty getDecompileCacheFile(); + @ApiStatus.Internal + @Input + protected abstract Property getMaxCachedFiles(); + + @ApiStatus.Internal + @Input + protected abstract Property getMaxCacheFileAge(); + // Injects @Inject protected abstract WorkerExecutor getWorkerExecutor(); @@ -191,6 +201,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @Inject protected abstract ProgressLoggerFactory getProgressLoggerFactory(); + @Nested + protected abstract Property getDaemonUtilsContext(); + // Prevent Gradle from running two gen sources tasks in parallel @ServiceReference(SyncTaskBuildService.NAME) abstract Property getSyncTask(); @@ -242,6 +255,11 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { getMappings().set(SourceMappingsService.create(getProject())); + getMaxCachedFiles().set(GradleUtils.getIntegerPropertyProvider(getProject(), Constants.Properties.DECOMPILE_CACHE_MAX_FILES).orElse(50_000)); + getMaxCacheFileAge().set(GradleUtils.getIntegerPropertyProvider(getProject(), Constants.Properties.DECOMPILE_CACHE_MAX_AGE).orElse(90)); + + getDaemonUtilsContext().set(getProject().getObjects().newInstance(DaemonUtils.Context.class, getProject())); + mustRunAfter(getProject().getTasks().withType(AbstractRemapJarTask.class)); } @@ -259,7 +277,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { try (var timer = new Timer("Decompiled sources")) { runWithoutCache(); } catch (Exception e) { - ExceptionUtil.processException(e, getProject()); + ExceptionUtil.processException(e, getDaemonUtilsContext().get()); throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e); } @@ -277,14 +295,22 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } // TODO ensure we have a lock on this file to prevent multiple tasks from running at the same time - // TODO handle being unable to read the cache file Files.createDirectories(cacheFile.getParent()); + if (Files.exists(cacheFile)) { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) { + // Success, cache exists and can be read + } catch (IOException e) { + getLogger().warn("Discarding invalid decompile cache file: {}", cacheFile, e); + Files.delete(cacheFile); + } + } + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) { runWithCache(fs.getRoot()); } } catch (Exception e) { - ExceptionUtil.processException(e, getProject()); + ExceptionUtil.processException(e, getDaemonUtilsContext().get()); throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e); } } @@ -293,13 +319,14 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { final Path classesInputJar = getClassesInputJar().getSingleFile().toPath(); final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath(); final Path classesOutputJar = getClassesOutputJar().getSingleFile().toPath(); - final var cacheRules = new CachedFileStoreImpl.CacheRules(50_000, Duration.ofDays(90)); + final var cacheRules = new CachedFileStoreImpl.CacheRules(getMaxCachedFiles().get(), Duration.ofDays(getMaxCacheFileAge().get())); final var decompileCache = new CachedFileStoreImpl<>(cacheRoot, CachedData.SERIALIZER, cacheRules); final String cacheKey = getCacheKey(); final CachedJarProcessor cachedJarProcessor = new CachedJarProcessor(decompileCache, cacheKey); final CachedJarProcessor.WorkRequest workRequest; getLogger().info("Decompile cache key: {}", cacheKey); + getLogger().debug("Decompile cache rules: {}", cacheRules); try (var timer = new Timer("Prepare job")) { workRequest = cachedJarProcessor.prepareJob(classesInputJar); diff --git a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java index 5e41a8af..31c8c0f2 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java +++ b/src/main/java/net/fabricmc/loom/task/RemapTaskConfiguration.java @@ -142,7 +142,8 @@ public abstract class RemapTaskConfiguration implements Runnable { final Jar jarTask = (Jar) getTasks().getByName(JavaPlugin.JAR_TASK_NAME); configuration.getArtifacts().removeIf(artifact -> { // if the artifact is built by the jar task, and has the same output path. - return artifact.getFile().getAbsolutePath().equals(jarTask.getArchiveFile().get().getAsFile().getAbsolutePath()) && artifact.getBuildDependencies().getDependencies(null).contains(jarTask); + return artifact.getFile().getAbsolutePath().equals(jarTask.getArchiveFile().get().getAsFile().getAbsolutePath()) + && (extension.isProjectIsolationActive() || artifact.getBuildDependencies().getDependencies(null).contains(jarTask)); }); } }); diff --git a/src/main/java/net/fabricmc/loom/task/service/MixinAPMappingService.java b/src/main/java/net/fabricmc/loom/task/service/MixinAPMappingService.java index 4f0430b8..55ea8a34 100644 --- a/src/main/java/net/fabricmc/loom/task/service/MixinAPMappingService.java +++ b/src/main/java/net/fabricmc/loom/task/service/MixinAPMappingService.java @@ -59,8 +59,6 @@ import net.fabricmc.tinyremapper.IMappingProvider; public class MixinAPMappingService extends Service { public static final ServiceType TYPE = new ServiceType<>(Options.class, MixinAPMappingService.class); - // TODO look into seeing if we can make this an option, it likely breaks project isolation. - private static final boolean INCLUDE_CROSS_PROJECT_MAPPINGS = true; // Again look into what the result of changing this would be. private static final boolean USE_ALL_SOURCE_SETS = true; private static final Logger LOGGER = LoggerFactory.getLogger(MixinAPMappingService.class); @@ -105,7 +103,8 @@ public class MixinAPMappingService extends Service { 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 2fa82e42..1c513e48 100644 --- a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -111,7 +111,7 @@ public class TinyRemapperService extends Service im options.getUselegacyMixinAP().set(legacyMixin); options.getKotlinClasspathService().set(KotlinClasspathService.createOptions(project)); options.getClasspath().from(classpath); - options.getKnownIndyBsms().set(extension.getKnownIndyBsms()); + options.getKnownIndyBsms().set(extension.getKnownIndyBsms().get().stream().sorted().toList()); options.getRemapperExtensions().set(extension.getRemapperExtensions()); }); } diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index 3394fc10..08e70b69 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -166,6 +166,8 @@ public class Constants { * Only set this when you have a good reason to do so, the default should be fine for almost all cases. */ public static final String RUNTIME_JAVA_COMPATIBILITY_VERSION = "fabric.loom.runtimeJavaCompatibilityVersion"; + public static final String DECOMPILE_CACHE_MAX_FILES = "fabric.loom.decompileCacheMaxFiles"; + public static final String DECOMPILE_CACHE_MAX_AGE = "fabric.loom.decompileCacheMaxAge"; public static final String ALLOW_MISMATCHED_PLATFORM_VERSION = "loom.allowMismatchedPlatformVersion"; public static final String IGNORE_DEPENDENCY_LOOM_VERSION_VALIDATION = "loom.ignoreDependencyLoomVersionValidation"; } diff --git a/src/main/java/net/fabricmc/loom/util/ExceptionUtil.java b/src/main/java/net/fabricmc/loom/util/ExceptionUtil.java index 71f36433..352cb506 100644 --- a/src/main/java/net/fabricmc/loom/util/ExceptionUtil.java +++ b/src/main/java/net/fabricmc/loom/util/ExceptionUtil.java @@ -31,7 +31,6 @@ import java.nio.file.Paths; import java.util.List; import java.util.function.BiFunction; -import org.gradle.api.Project; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,7 +59,7 @@ public final class ExceptionUtil { return constructor.apply(descriptiveMessage, cause); } - public static void processException(Throwable e, Project project) { + public static void processException(Throwable e, DaemonUtils.Context context) { Throwable cause = e; boolean unrecoverable = false; @@ -68,7 +67,7 @@ public final class ExceptionUtil { if (cause instanceof FileSystemUtil.UnrecoverableZipException) { unrecoverable = true; } else if (cause instanceof FileSystemException fse) { - printFileLocks(fse.getFile(), project); + printFileLocks(fse.getFile()); break; } @@ -76,11 +75,11 @@ public final class ExceptionUtil { } if (unrecoverable) { - DaemonUtils.tryStopGradleDaemon(project); + DaemonUtils.tryStopGradleDaemon(context); } } - private static void printFileLocks(String filename, Project project) { + private static void printFileLocks(String filename) { final Path path = Paths.get(filename); if (!Files.exists(path)) { @@ -100,13 +99,13 @@ public final class ExceptionUtil { return; } - final ProcessUtil processUtil = ProcessUtil.create(project); + final ProcessUtil processUtil = ProcessUtil.create(LOGGER.isInfoEnabled() ? ProcessUtil.ArgumentVisibility.SHOW_SENSITIVE : ProcessUtil.ArgumentVisibility.HIDE); final String noun = processes.size() == 1 ? "process has" : "processes have"; - project.getLogger().error("The following {} a lock on the file '{}':", noun, path); + LOGGER.error("The following {} a lock on the file '{}':", noun, path); for (ProcessHandle process : processes) { - project.getLogger().error(processUtil.printWithParents(process)); + LOGGER.error(processUtil.printWithParents(process)); } } } diff --git a/src/main/java/net/fabricmc/loom/util/LibraryLocationLogger.java b/src/main/java/net/fabricmc/loom/util/LibraryLocationLogger.java index ce193de4..1d328c57 100644 --- a/src/main/java/net/fabricmc/loom/util/LibraryLocationLogger.java +++ b/src/main/java/net/fabricmc/loom/util/LibraryLocationLogger.java @@ -28,7 +28,7 @@ import java.util.List; import com.google.common.base.Preconditions; import com.google.gson.Gson; -import kotlinx.metadata.jvm.KotlinClassMetadata; +import kotlin.metadata.jvm.KotlinClassMetadata; import org.apache.commons.io.FileUtils; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.commons.ClassRemapper; diff --git a/src/main/java/net/fabricmc/loom/util/ProcessUtil.java b/src/main/java/net/fabricmc/loom/util/ProcessUtil.java index 8b36e549..6b427526 100644 --- a/src/main/java/net/fabricmc/loom/util/ProcessUtil.java +++ b/src/main/java/net/fabricmc/loom/util/ProcessUtil.java @@ -37,18 +37,22 @@ import org.slf4j.LoggerFactory; import net.fabricmc.loom.nativeplatform.LoomNativePlatform; import net.fabricmc.loom.nativeplatform.LoomNativePlatformException; -public record ProcessUtil(LogLevel logLevel) { +public record ProcessUtil(ArgumentVisibility argumentVisibility) { private static final String EXPLORER_COMMAND = "C:\\Windows\\explorer.exe"; private static final Logger LOGGER = LoggerFactory.getLogger(ProcessUtil.class); public static ProcessUtil create(Project project) { - return new ProcessUtil(project.getGradle().getStartParameter().getLogLevel()); + return create(ArgumentVisibility.get(project)); + } + + public static ProcessUtil create(ArgumentVisibility argumentVisibility) { + return new ProcessUtil(argumentVisibility); } public String printWithParents(ProcessHandle handle) { String result = printWithParents(handle, 0).trim(); - if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) { + if (argumentVisibility == ArgumentVisibility.HIDE) { return "Run with --info or --debug to show arguments, may reveal sensitive info\n" + result; } @@ -75,7 +79,7 @@ public record ProcessUtil(LogLevel logLevel) { } private Optional getProcessArguments(ProcessHandle handle) { - if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) { + if (argumentVisibility != ArgumentVisibility.SHOW_SENSITIVE) { return Optional.empty(); } @@ -117,4 +121,14 @@ public record ProcessUtil(LogLevel logLevel) { return Optional.of(joiner.toString()); } + + public enum ArgumentVisibility { + HIDE, + SHOW_SENSITIVE; + + static ArgumentVisibility get(Project project) { + final LogLevel logLevel = project.getGradle().getStartParameter().getLogLevel(); + return (logLevel == LogLevel.INFO || logLevel == LogLevel.DEBUG) ? SHOW_SENSITIVE : HIDE; + } + } } diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java index 3f90bea9..428579b2 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java @@ -39,6 +39,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; import dev.architectury.loom.metadata.ModMetadataFile; import dev.architectury.loom.metadata.ModMetadataFiles; +import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; @@ -134,8 +135,8 @@ public final class FabricModJsonFactory { } @Nullable - public static FabricModJson createFromSourceSetsNullable(SourceSet... sourceSets) throws IOException { - final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, sourceSets); + public static FabricModJson createFromSourceSetsNullable(Project project, SourceSet... sourceSets) throws IOException { + final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, project, sourceSets); if (file == null) { // Try another mod metadata file if fabric.mod.json wasn't found. @@ -149,7 +150,7 @@ public final class FabricModJsonFactory { } try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { - return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(sourceSets)); + return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(project, sourceSets)); } catch (JsonSyntaxException e) { LOGGER.warn("Failed to parse fabric.mod.json: {}", file.getAbsolutePath()); return null; diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java index 923c7a30..43e0e474 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonHelpers.java @@ -48,7 +48,7 @@ public class FabricModJsonHelpers { } try { - final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(sourceSets.toArray(SourceSet[]::new)); + final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(project, sourceSets.toArray(SourceSet[]::new)); if (fabricModJson != null) { return List.of(fabricModJson); diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonSource.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonSource.java index 9ff5a7d6..1d185e28 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonSource.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonSource.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import net.fabricmc.loom.util.ZipUtils; @@ -56,14 +57,14 @@ public interface FabricModJsonSource { } } - record SourceSetSource(SourceSet... sourceSets) implements FabricModJsonSource { + record SourceSetSource(Project project, SourceSet... sourceSets) implements FabricModJsonSource { @Override public byte[] read(String path) throws IOException { return Files.readAllBytes(findFile(path).toPath()); } private File findFile(String path) throws IOException { - final File file = SourceSetHelper.findFirstFileInResource(path, sourceSets); + final File file = SourceSetHelper.findFirstFileInResource(path, project, sourceSets); if (file == null) { throw new FileNotFoundException("Could not find: " + path); diff --git a/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java b/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java index 45dfd90c..f5b5b822 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/GradleUtils.java @@ -25,24 +25,18 @@ package net.fabricmc.loom.util.gradle; import java.io.File; -import java.lang.reflect.Field; import java.util.function.Consumer; import org.gradle.api.Project; -import org.gradle.api.artifacts.ProjectDependency; import org.gradle.api.file.RegularFileProperty; -import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency; -import org.gradle.api.internal.catalog.DelegatingProjectDependency; import org.gradle.api.invocation.Gradle; import org.gradle.api.provider.Provider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.util.Constants; public final class GradleUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(GradleUtils.class); - private GradleUtils() { } @@ -71,6 +65,13 @@ public final class GradleUtils { } public static Provider getBooleanPropertyProvider(Project project, String key) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + + if (extension.isProjectIsolationActive()) { + // TODO write a custom property parser for isolated projects + return project.provider(() -> false); + } + // Works around https://github.com/gradle/gradle/issues/23572 return project.provider(() -> { final Object value = project.findProperty(key); @@ -87,10 +88,37 @@ public final class GradleUtils { }); } + public static Provider getIntegerPropertyProvider(Project project, String key) { + return project.provider(() -> { + final Object value = project.findProperty(key); + + if (value == null) { + return null; + } + + try { + return Integer.parseInt(value.toString()); + } catch (final NumberFormatException ex) { + throw new IllegalArgumentException("Property " + key + " must be an integer", ex); + } + }); + } + public static boolean getBooleanProperty(Project project, String key) { return getBooleanPropertyProvider(project, key).getOrElse(false); } + public static Object getProperty(Project project, String key) { + LoomGradleExtension extension = LoomGradleExtension.get(project); + + if (extension.isProjectIsolationActive()) { + // TODO write a custom property parser for isolated projects + return null; + } + + return project.findProperty(key); + } + // A hack to include the given file in the configuration cache input // this ensures that configuration cache is invalidated when the file changes public static File configurationInputFile(Project project, File file) { @@ -98,33 +126,4 @@ public final class GradleUtils { property.set(file); return property.getAsFile().get(); } - - // Get the project from the field with reflection to suppress the deprecation warning. - // If you hate it find a solution yourself and make a PR, I'm getting a bit tired of chasing Gradle updates - public static Project getDependencyProject(ProjectDependency projectDependency) { - if (projectDependency instanceof DefaultProjectDependency) { - try { - final Class clazz = DefaultProjectDependency.class; - final Field dependencyProject = clazz.getDeclaredField("dependencyProject"); - dependencyProject.setAccessible(true); - return (Project) dependencyProject.get(projectDependency); - } catch (NoSuchFieldException | IllegalAccessException e) { - LOGGER.warn("Failed to reflect DefaultProjectDependency", e); - } - } else if (projectDependency instanceof DelegatingProjectDependency) { - try { - final Class clazz = DelegatingProjectDependency.class; - final Field delgeate = clazz.getDeclaredField("delegate"); - delgeate.setAccessible(true); - return getDependencyProject((ProjectDependency) delgeate.get(projectDependency)); - } catch (NoSuchFieldException | IllegalAccessException e) { - LOGGER.warn("Failed to reflect DelegatingProjectDependency", e); - } - } - - // Just fallback and trigger the warning, this will break in Gradle 9 - final Project project = projectDependency.getDependencyProject(); - LOGGER.warn("Loom was unable to suppress the deprecation warning for ProjectDependency#getDependencyProject, if you are on the latest version of Loom please report this issue to the Loom developers and provide the error above, this WILL stop working in a future Gradle version."); - return project; - } } diff --git a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java index a49cac45..e605648c 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/SourceSetHelper.java @@ -268,11 +268,11 @@ public final class SourceSetHelper { } @Nullable - public static File findFileInResource(SourceSet sourceSet, String path) { + public static File findFileInResource(Project project, SourceSet sourceSet, String path) { + Objects.requireNonNull(project); Objects.requireNonNull(sourceSet); Objects.requireNonNull(path); - final Project project = getSourceSetProject(sourceSet); final LoomGradleExtension extension = LoomGradleExtension.get(project); if (extension.isConfigurationCacheActive()) { @@ -298,9 +298,9 @@ public final class SourceSetHelper { } @Nullable - public static File findFirstFileInResource(String path, SourceSet... sourceSets) { + public static File findFirstFileInResource(String path, Project project, SourceSet... sourceSets) { for (SourceSet sourceSet : sourceSets) { - File file = findFileInResource(sourceSet, path); + File file = findFileInResource(project, sourceSet, path); if (file != null) { return file; diff --git a/src/main/java/net/fabricmc/loom/util/gradle/WorkerDaemonClientsManagerHelper.java b/src/main/java/net/fabricmc/loom/util/gradle/WorkerDaemonClientsManagerHelper.java index 07d2f037..79a941d7 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/WorkerDaemonClientsManagerHelper.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/WorkerDaemonClientsManagerHelper.java @@ -32,7 +32,6 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.gradle.api.Transformer; -import org.gradle.process.internal.JvmOptions; import org.gradle.workers.internal.DaemonForkOptions; import org.gradle.workers.internal.WorkerDaemonClientsManager; @@ -88,8 +87,11 @@ public class WorkerDaemonClientsManagerHelper { try { Method getJvmOptions = forkOptions.getClass().getDeclaredMethod("getJvmOptions"); getJvmOptions.setAccessible(true); - JvmOptions jvmOptions = (JvmOptions) getJvmOptions.invoke(forkOptions); - return jvmOptions.getMutableSystemProperties(); + Object jvmOptions = getJvmOptions.invoke(forkOptions); + Method getMutableSystemProperties = jvmOptions.getClass().getDeclaredMethod("getMutableSystemProperties"); + getMutableSystemProperties.setAccessible(true); + //noinspection unchecked + return (Map) getMutableSystemProperties.invoke(jvmOptions); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException("Failed to daemon system properties", e); } diff --git a/src/main/java/net/fabricmc/loom/util/gradle/daemon/DaemonUtils.java b/src/main/java/net/fabricmc/loom/util/gradle/daemon/DaemonUtils.java index 79ff1bc6..2ec97583 100644 --- a/src/main/java/net/fabricmc/loom/util/gradle/daemon/DaemonUtils.java +++ b/src/main/java/net/fabricmc/loom/util/gradle/daemon/DaemonUtils.java @@ -28,14 +28,16 @@ import java.nio.file.Path; import java.util.List; import java.util.UUID; +import javax.inject.Inject; + import org.gradle.api.Project; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; import org.gradle.cache.FileLockManager; import org.gradle.internal.file.Chmod; import org.gradle.internal.remote.internal.RemoteConnection; import org.gradle.internal.remote.internal.inet.TcpOutgoingConnector; import org.gradle.internal.serialize.Serializers; -import org.gradle.internal.service.ServiceRegistry; -import org.gradle.invocation.DefaultGradle; import org.gradle.launcher.daemon.client.DaemonClientConnection; import org.gradle.launcher.daemon.client.StopDispatcher; import org.gradle.launcher.daemon.protocol.DaemonMessageSerializer; @@ -63,17 +65,17 @@ public final class DaemonUtils { /** * Request the Gradle daemon to stop when it becomes idle. */ - public static void tryStopGradleDaemon(Project project) { + public static void tryStopGradleDaemon(DaemonUtils.Context context) { try { - stopWhenIdle(project); + stopWhenIdle(context); } catch (Throwable t) { LOGGER.error("Failed to request the Gradle demon to stop", t); } } @VisibleForTesting - public static boolean stopWhenIdle(Project project) { - DaemonInfo daemonInfo = findCurrentDaemon(project); + public static boolean stopWhenIdle(DaemonUtils.Context context) { + DaemonInfo daemonInfo = findCurrentDaemon(context); if (daemonInfo == null) { return false; @@ -98,14 +100,13 @@ public final class DaemonUtils { } @Nullable - private static DaemonInfo findCurrentDaemon(Project project) { + private static DaemonInfo findCurrentDaemon(DaemonUtils.Context context) { // Gradle maintains a list of running daemons in a registry.bin file. - final Path registryBin = project.getGradle().getGradleUserHomeDir().toPath().resolve("daemon").resolve(GradleVersion.current().getVersion()).resolve("registry.bin"); - project.getLogger().lifecycle("Looking for daemon in: " + registryBin); + final Path registryBin = Path.of(context.getRegistryBin().get()); + LOGGER.info("Looking for daemon in: {}", registryBin); // We can use a PersistentDaemonRegistry to read this - final ServiceRegistry services = ((DefaultGradle) project.getGradle()).getServices(); - final DaemonRegistry registry = new PersistentDaemonRegistry(registryBin.toFile(), services.get(FileLockManager.class), services.get(Chmod.class)); + final DaemonRegistry registry = new PersistentDaemonRegistry(registryBin.toFile(), context.getFileLockManager(), context.getChmod()); final long pid = ProcessHandle.current().pid(); final List runningDaemons = registry.getAll(); @@ -121,4 +122,33 @@ public final class DaemonUtils { LOGGER.warn("Could not find current process in daemon registry: {}", registryBin); return null; } + + public abstract static class Context { + @Input + protected abstract Property getRegistryBin(); + + @Inject + protected abstract FileLockManager getFileLockManager(); + + @Inject + protected abstract Chmod getChmod(); + + @SuppressWarnings("unused") + @Inject + public Context(Project project) { + getRegistryBin().set(Context.getRegistryBinPathName(project)); + } + + public static Context fromProject(Project project) { + return project.getObjects().newInstance(Context.class, project); + } + + private static String getRegistryBinPathName(Project project) { + return project.getGradle().getGradleUserHomeDir().toPath() + .resolve("daemon") + .resolve(GradleVersion.current().getVersion()) + .resolve("registry.bin") + .toAbsolutePath().toString(); + } + } } diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java index 9672dc90..3f89a7f5 100644 --- a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinClasspathService.java @@ -61,17 +61,16 @@ public final class KotlinClasspathService extends Service createOptions(Project project, String kotlinVersion, String kotlinMetadataVersion) { + private static Provider createOptions(Project project, String kotlinVersion) { // Create a detached config to resolve the kotlin std lib for the provided version. Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration( project.getDependencies().create("org.jetbrains.kotlin:kotlin-stdlib:" + kotlinVersion), // Load kotlinx-metadata-jvm like this to work around: https://github.com/gradle/gradle/issues/14727 - project.getDependencies().create("org.jetbrains.kotlinx:kotlinx-metadata-jvm:" + kotlinMetadataVersion) + project.getDependencies().create("org.jetbrains.kotlin:kotlin-metadata-jvm:" + kotlinVersion) ); return TYPE.create(project, options -> { diff --git a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java index 76a96215..8de617f3 100644 --- a/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java +++ b/src/main/java/net/fabricmc/loom/util/kotlin/KotlinPluginUtils.java @@ -29,7 +29,6 @@ import java.io.InputStream; import java.io.UncheckedIOException; import java.util.Properties; -import kotlinx.metadata.jvm.KotlinClassMetadata; import org.gradle.api.Project; public class KotlinPluginUtils { @@ -56,8 +55,4 @@ public class KotlinPluginUtils { return props.getProperty(property); } - - public static String getKotlinMetadataVersion() { - return KotlinClassMetadata.class.getPackage().getImplementationVersion().split("-")[0]; - } } diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt index 483d9f6d..f20054c4 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassMetadataRemappingAnnotationVisitor.kt @@ -24,13 +24,13 @@ package net.fabricmc.loom.kotlin.remapping -import kotlinx.metadata.jvm.KotlinClassMetadata -import kotlinx.metadata.jvm.Metadata import org.objectweb.asm.AnnotationVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.commons.Remapper import org.objectweb.asm.tree.AnnotationNode import org.slf4j.LoggerFactory +import kotlin.metadata.jvm.KotlinClassMetadata +import kotlin.metadata.jvm.Metadata class KotlinClassMetadataRemappingAnnotationVisitor( private val remapper: Remapper, @@ -62,8 +62,14 @@ class KotlinClassMetadataRemappingAnnotationVisitor( "is using (${KotlinVersion.CURRENT}).", ) } + val metadata = KotlinClassMetadata.readLenient(header) + if (metadata.version.major < 1 || (metadata.version.major == 1 && metadata.version.minor < 4)) { + logger.warn("$className is not supported by kotlin metadata remapping (version: ${metadata.version})") + accept(next) + return + } - when (val metadata = KotlinClassMetadata.readLenient(header)) { + when (metadata) { is KotlinClassMetadata.Class -> { var klass = metadata.kmClass klass = KotlinClassRemapper(remapper).remap(klass) diff --git a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassRemapper.kt b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassRemapper.kt index d77a0d48..4cc9f42b 100644 --- a/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassRemapper.kt +++ b/src/main/kotlin/net/fabricmc/loom/kotlin/remapping/KotlinClassRemapper.kt @@ -24,35 +24,35 @@ package net.fabricmc.loom.kotlin.remapping -import kotlinx.metadata.ClassName -import kotlinx.metadata.ExperimentalContextReceivers -import kotlinx.metadata.KmAnnotation -import kotlinx.metadata.KmClass -import kotlinx.metadata.KmClassifier -import kotlinx.metadata.KmConstructor -import kotlinx.metadata.KmFlexibleTypeUpperBound -import kotlinx.metadata.KmFunction -import kotlinx.metadata.KmLambda -import kotlinx.metadata.KmPackage -import kotlinx.metadata.KmProperty -import kotlinx.metadata.KmType -import kotlinx.metadata.KmTypeAlias -import kotlinx.metadata.KmTypeParameter -import kotlinx.metadata.KmTypeProjection -import kotlinx.metadata.KmValueParameter -import kotlinx.metadata.isLocalClassName -import kotlinx.metadata.jvm.JvmFieldSignature -import kotlinx.metadata.jvm.JvmMethodSignature -import kotlinx.metadata.jvm.annotations -import kotlinx.metadata.jvm.fieldSignature -import kotlinx.metadata.jvm.getterSignature -import kotlinx.metadata.jvm.localDelegatedProperties -import kotlinx.metadata.jvm.setterSignature -import kotlinx.metadata.jvm.signature -import kotlinx.metadata.jvm.syntheticMethodForAnnotations -import kotlinx.metadata.jvm.syntheticMethodForDelegate -import kotlinx.metadata.jvm.toJvmInternalName import org.objectweb.asm.commons.Remapper +import kotlin.metadata.ClassName +import kotlin.metadata.ExperimentalContextReceivers +import kotlin.metadata.KmAnnotation +import kotlin.metadata.KmClass +import kotlin.metadata.KmClassifier +import kotlin.metadata.KmConstructor +import kotlin.metadata.KmFlexibleTypeUpperBound +import kotlin.metadata.KmFunction +import kotlin.metadata.KmLambda +import kotlin.metadata.KmPackage +import kotlin.metadata.KmProperty +import kotlin.metadata.KmType +import kotlin.metadata.KmTypeAlias +import kotlin.metadata.KmTypeParameter +import kotlin.metadata.KmTypeProjection +import kotlin.metadata.KmValueParameter +import kotlin.metadata.isLocalClassName +import kotlin.metadata.jvm.JvmFieldSignature +import kotlin.metadata.jvm.JvmMethodSignature +import kotlin.metadata.jvm.annotations +import kotlin.metadata.jvm.fieldSignature +import kotlin.metadata.jvm.getterSignature +import kotlin.metadata.jvm.localDelegatedProperties +import kotlin.metadata.jvm.setterSignature +import kotlin.metadata.jvm.signature +import kotlin.metadata.jvm.syntheticMethodForAnnotations +import kotlin.metadata.jvm.syntheticMethodForDelegate +import kotlin.metadata.jvm.toJvmInternalName @OptIn(ExperimentalContextReceivers::class) class KotlinClassRemapper(private val remapper: Remapper) { 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 66919c0f..94e9d293 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/DataGenerationTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/DataGenerationTest.groovy @@ -29,10 +29,20 @@ import spock.lang.Unroll 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.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" + } + """ + @Unroll def "dataGeneration (gradle #version)"() { setup: @@ -41,14 +51,7 @@ class DataGenerationTest extends Specification implements GradleProjectTestTrait fabricApi { configureDataGeneration() } - - 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" - } - ''' + ''' + DEPENDENCIES when: def result = gradle.run(task: "runDatagen") @@ -80,17 +83,8 @@ class DataGenerationTest extends Specification implements GradleProjectTestTrait } } - 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" - - modDatagenImplementation fabricApi.module("fabric-data-generation-api-v1", "0.90.0+1.20.2") - } - println("%%" + loom.runs.datagen.configName + "%%") - ''' + ''' + DEPENDENCIES when: def result = gradle.run(task: "runDatagen") @@ -101,4 +95,111 @@ class DataGenerationTest extends Specification implements GradleProjectTestTrait where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "client dataGeneration (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE) + gradle.buildGradle << ''' + fabricApi { + configureDataGeneration { + client = true + } + } + ''' + DEPENDENCIES + when: + def result = gradle.run(task: "runDatagen") + + then: + result.task(":runDatagen").outcome == SUCCESS + } + + @Unroll + def "client dataGeneration sourceset (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE) + gradle.buildGradle << ''' + // Must configure the main mod + loom.mods { + "example" { + sourceSet sourceSets.main + } + } + + fabricApi { + configureDataGeneration { + createSourceSet = true + createRunConfiguration = true + modId = "example-datagen" + strictValidation = true + client = true + } + } + ''' + DEPENDENCIES + when: + def result = gradle.run(task: "runDatagen") + + then: + result.task(":runDatagen").outcome == SUCCESS + } + + @Unroll + def "split client dataGeneration (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE) + gradle.buildGradle << ''' + loom { + splitEnvironmentSourceSets() + mods { + "example" { + sourceSet sourceSets.main + sourceSet sourceSets.client + } + } + } + + fabricApi { + configureDataGeneration { + client = true + } + } + ''' + DEPENDENCIES + when: + def result = gradle.run(task: "runDatagen") + + then: + result.task(":runDatagen").outcome == SUCCESS + } + + @Unroll + def "split client dataGeneration sourceset (gradle #version)"() { + setup: + def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE) + gradle.buildGradle << ''' + loom { + splitEnvironmentSourceSets() + mods { + "example" { + sourceSet sourceSets.main + sourceSet sourceSets.client + } + } + } + + fabricApi { + configureDataGeneration { + createSourceSet = true + createRunConfiguration = true + modId = "example-datagen" + strictValidation = true + client = true + } + } + ''' + DEPENDENCIES + when: + def result = gradle.run(task: "runDatagen") + + then: + result.task(":runDatagen").outcome == SUCCESS + } } 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 921af92a..c1397f5d 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: "41bc64cd617f03d49ecc4a4f7788cb65d465415c", + commit: "70277babddfaf52ee30013af94764da19473b3b1", version: version, patch: "fabric_api" ) @@ -63,7 +63,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait { .replace('id "fabric-loom" version "1.6.11"', 'id "dev.architectury.loom"') .replace('"fabric-loom"', '"dev.architectury.loom"') + mixinApPatch - def minecraftVersion = "1.21" + def minecraftVersion = "1.21.4-pre3" def server = ServerRunner.create(gradle.projectDir, minecraftVersion) .withMod(gradle.getOutputFile("fabric-api-999.0.0.jar")) @@ -80,7 +80,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait { dependencies { minecraft "com.mojang:minecraft:${minecraftVersion}" - mappings "net.fabricmc:yarn:${minecraftVersion}+build.1:v2" + mappings "net.fabricmc:yarn:${minecraftVersion}+build.2:v2" modImplementation "net.fabricmc.fabric-api:fabric-api:999.0.0" } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/MultiMcVersionTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/MultiMcVersionTest.groovy index d6280a46..3c8d531b 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/MultiMcVersionTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/MultiMcVersionTest.groovy @@ -51,6 +51,7 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait def "build (gradle #version)"() { setup: def gradle = gradleProject(project: "multi-mc-versions", version: version) + gradle.buildSrc("multiMcVersions", false) versions.forEach { // Make dir as its now required by Gradle @@ -58,10 +59,9 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait } when: - def result = gradle.run(tasks: "build") + def result = gradle.run(tasks: "build", isloatedProjects: true, configureOnDemand: true) then: - result.task(":build").outcome == SUCCESS versions.forEach { result.task(":$it:build").outcome == SUCCESS } @@ -69,4 +69,35 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait where: version << STANDARD_TEST_VERSIONS } + + @Unroll + def "configure on demand (gradle #version)"() { + setup: + def gradle = gradleProject(project: "multi-mc-versions", version: version) + gradle.buildSrc("multiMcVersions", false) + + versions.forEach { + // Make dir as its now required by Gradle + new File(gradle.projectDir, it).mkdir() + } + + when: + def result = gradle.run( + tasks: ":fabric-1.19.3:build", + isloatedProjects: true, + configureOnDemand: true, + // See: https://github.com/gradle/gradle/issues/30401 + // By default parallel configuration of all projects is preferred. + args: [ + "-Dorg.gradle.internal.isolated-projects.configure-on-demand.tasks=true" + ]) + + then: + result.task(":fabric-1.19.3:build").outcome == SUCCESS + // Ensure that loom is only loaded once. + result.output.count("Fabric Loom:") == 1 + + where: + version << STANDARD_TEST_VERSIONS + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/GradleUtilsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/multiMcVersions/TestPlugin.groovy similarity index 56% rename from src/test/groovy/net/fabricmc/loom/test/unit/GradleUtilsTest.groovy rename to src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/multiMcVersions/TestPlugin.groovy index 20f9c96f..f0d2369a 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/GradleUtilsTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/multiMcVersions/TestPlugin.groovy @@ -22,38 +22,26 @@ * SOFTWARE. */ -package net.fabricmc.loom.test.unit +package net.fabricmc.loom.test.integration.buildSrc.multiMcVersions -import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency -import org.gradle.api.internal.catalog.DelegatingProjectDependency -import org.gradle.api.internal.project.ProjectInternal -import spock.lang.Specification +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.BasePluginExtension -import net.fabricmc.loom.util.gradle.GradleUtils +class TestPlugin implements Plugin { + @Override + void apply(Project project) { + project.group = "com.example" + project.version = "1.0.0" -class GradleUtilsTest extends Specification { - def "get default project dependency"() { - given: - def project = Mock(ProjectInternal) - def dependency = new DefaultProjectDependency(project, false) + project.getExtensions().configure(BasePluginExtension.class) { + it.archivesName = project.rootProject.isolated.name + "-" + project.name + } - when: - def result = GradleUtils.getDependencyProject(dependency) + def minecraftVersion = project.name.substring(7) - then: - result == project - } - - def "get delegated project dependency"() { - given: - def project = Mock(ProjectInternal) - def dependency = new DefaultProjectDependency(project, true) - def delegate = new DelegatingProjectDependency(null, dependency) - - when: - def result = GradleUtils.getDependencyProject(delegate) - - then: - result == project + project.getDependencies().add("minecraft", "com.mojang:minecraft:$minecraftVersion") + project.getDependencies().add("mappings", "net.fabricmc:yarn:$minecraftVersion+build.1:v2") + project.getDependencies().add("modImplementation", "net.fabricmc:fabric-loader:0.16.9") } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/stopDaemon/TestPlugin.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/stopDaemon/TestPlugin.groovy index 0c885d3c..b2743e41 100644 --- a/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/stopDaemon/TestPlugin.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/stopDaemon/TestPlugin.groovy @@ -85,7 +85,7 @@ class TestPlugin implements Plugin { def future = handler.daemonConnection.thenAccept { it.waitForAndProcessStop() } // Stop the daemon - def result = DaemonUtils.stopWhenIdle(project) + def result = DaemonUtils.stopWhenIdle(DaemonUtils.Context.fromProject(project)) // Wait for the connection to be processed, this should have already happened, as the above call is blocking future.join() diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ProcessUtilTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ProcessUtilTest.groovy index 76a96ea3..055a91f2 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/ProcessUtilTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ProcessUtilTest.groovy @@ -24,7 +24,6 @@ package net.fabricmc.loom.test.unit -import org.gradle.api.logging.LogLevel import spock.lang.Specification import net.fabricmc.loom.util.ProcessUtil @@ -32,7 +31,7 @@ import net.fabricmc.loom.util.ProcessUtil class ProcessUtilTest extends Specification { def "print process info"() { when: - def output = new ProcessUtil(LogLevel.DEBUG).printWithParents(ProcessHandle.current()) + def output = new ProcessUtil(ProcessUtil.ArgumentVisibility.SHOW_SENSITIVE).printWithParents(ProcessHandle.current()) then: // Just a simple check to see if the output is not empty diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy index 98b4042a..2c938ae9 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/kotlin/KotlinRemapperClassloaderTest.groovy @@ -30,7 +30,6 @@ import org.objectweb.asm.tree.ClassNode import spock.lang.Specification import net.fabricmc.loom.util.kotlin.KotlinClasspath -import net.fabricmc.loom.util.kotlin.KotlinPluginUtils import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader import net.fabricmc.tinyremapper.api.TrClass import net.fabricmc.tinyremapper.api.TrEnvironment @@ -38,9 +37,8 @@ import net.fabricmc.tinyremapper.api.TrRemapper class KotlinRemapperClassloaderTest extends Specification { private static String KOTLIN_VERSION = KotlinVersion.CURRENT.toString() - private static String KOTLIN_METADATA_VERSION = KotlinPluginUtils.kotlinMetadataVersion private static String KOTLIN_URL = "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/${KOTLIN_VERSION}/kotlin-stdlib-${KOTLIN_VERSION}.jar" - private static String KOTLIN_METADATA_URL = "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-metadata-jvm/${KOTLIN_METADATA_VERSION}/kotlinx-metadata-jvm-${KOTLIN_METADATA_VERSION}.jar" + private static String KOTLIN_METADATA_URL = "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-metadata-jvm/${KOTLIN_VERSION}/kotlin-metadata-jvm-${KOTLIN_VERSION}.jar" def "Test Kotlin Remapper Classloader"() { given: diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy index b9183787..d14673be 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleProjectTestTrait.groovy @@ -180,6 +180,10 @@ trait GradleProjectTestTrait { args << "-Dorg.gradle.unsafe.isolated-projects=true" } + if (options.configureOnDemand) { + args << "--configure-on-demand" + } + args.addAll(options.tasks ?: []) args << "--stacktrace" @@ -278,7 +282,7 @@ trait GradleProjectTestTrait { return file } - void buildSrc(String name) { + void buildSrc(String name, boolean apply = true) { useBuildSrc = true def buildSrcDir = new File(projectDir, "buildSrc") @@ -305,24 +309,26 @@ trait GradleProjectTestTrait { rootProject.name='loom-test-plugin' ''' - // Patch the new plugin into the end of the plugins block - def matcher = buildGradle.text =~ /(?s)plugins \{(?.*?)}/ - assert matcher.find() - def ids = matcher.group("ids") - - def pluginBlock = """ - plugins { - ${ids} - id 'loom-test-plugin' - } - """ - - buildGradle.text = buildGradle.text.replaceAll("(?s)(plugins \\{.*?})", pluginBlock) - def sourceSrc = new File("src/test/groovy/net/fabricmc/loom/test/integration/buildSrc/" + name) def targetSrc = new File(buildSrcDir, "src/main/groovy/net/fabricmc/loom/test/integration/buildSrc/" + name) FileUtils.copyDirectory(sourceSrc, targetSrc) + + if (apply) { + // Patch the new plugin into the end of the plugins block + def matcher = buildGradle.text =~ /(?s)plugins \{(?.*?)}/ + assert matcher.find() + def ids = matcher.group("ids") + + def pluginBlock = """ + plugins { + ${ids} + id 'loom-test-plugin' + } + """ + + buildGradle.text = buildGradle.text.replaceAll("(?s)(plugins \\{.*?})", pluginBlock) + } } void writeBuildSrcDeps(GradleRunner runner) { diff --git a/src/test/resources/patches/fabric_api.patch b/src/test/resources/patches/fabric_api.patch index 5e8c2476..b2b77775 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 41bc64cd617f03d49ecc4a4f7788cb65d465415c) -+++ b/build.gradle (date 1718312645477) +--- a/build.gradle (revision 70277babddfaf52ee30013af94764da19473b3b1) ++++ b/build.gradle (date 1732875235843) @@ -13,7 +13,7 @@ def ENV = System.getenv() @@ -34,5 +34,25 @@ diff --git a/build.gradle b/build.gradle - return version + "+" + latestCommits.get(0).id.substring(0, 8) + DigestUtils.sha256Hex(project.rootProject.minecraft_version).substring(0, 2) + return "999.0.0" } - + 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) +- } + } + + tasks.withType(ProcessResources).configureEach { diff --git a/src/test/resources/projects/kotlin/build.gradle.kts b/src/test/resources/projects/kotlin/build.gradle.kts index 90e74c35..9a5f1c2e 100644 --- a/src/test/resources/projects/kotlin/build.gradle.kts +++ b/src/test/resources/projects/kotlin/build.gradle.kts @@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions plugins { - kotlin("jvm") version "1.9.22" - kotlin("plugin.serialization") version "1.9.22" + kotlin("jvm") version "2.0.21" + kotlin("plugin.serialization") version "2.0.21" id("dev.architectury.loom") `maven-publish` } @@ -31,8 +31,8 @@ version = "0.0.1" dependencies { minecraft(group = "com.mojang", name = "minecraft", version = "1.16.5") mappings(group = "net.fabricmc", name = "yarn", version = "1.16.5+build.5", classifier = "v2") - modImplementation("net.fabricmc:fabric-loader:0.12.12") - modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.10.17+kotlin.1.9.22") + modImplementation("net.fabricmc:fabric-loader:0.16.9") + modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.12.3+kotlin.2.0.21") } publishing { diff --git a/src/test/resources/projects/multi-mc-versions/build.gradle b/src/test/resources/projects/multi-mc-versions/build.gradle deleted file mode 100644 index 1c8c563d..00000000 --- a/src/test/resources/projects/multi-mc-versions/build.gradle +++ /dev/null @@ -1,64 +0,0 @@ -import groovy.json.JsonSlurper - -plugins { - id "java" - id 'dev.architectury.loom' apply false -} - -allprojects { - group = project.maven_group - version = project.mod_version -} - -ext { - yarnMeta = new JsonSlurper().parse(new URL("https://meta.fabricmc.net/v2/versions/yarn")) -} - -def getMappingVersion(String mcVersion) { - return rootProject.yarnMeta.find { it.gameVersion == mcVersion }.version -} - -subprojects { - apply plugin: "dev.architectury.loom" - - base { - archivesName = rootProject.name + "-" + project.name - } - - def minecraft_version = project.name.substring(7) - def yarn_mappings = getMappingVersion(minecraft_version) - - dependencies { - // To change the versions see the gradle.properties files - minecraft "com.mojang:minecraft:$minecraft_version" - mappings "net.fabricmc:yarn:$yarn_mappings:v2" - modImplementation "net.fabricmc:fabric-loader:$loader_version" - } - - jar { - archiveClassifier.set "dev" - } - - // Just use the source from the root project - compileJava { - source(sourceSets.main.java.srcDirs) - } - - processResources { - from(rootProject.sourceSets.main.resources) - def version = project.version - def mcVersion = minecraft_version - def loaderVersion = project.loader_version - - inputs.property 'version', version - inputs.property 'minecraft_version', mcVersion - inputs.property 'loader_version', loaderVersion - - filesMatching("fabric.mod.json") { - expand 'version': version, 'minecraft_version': mcVersion, 'loader_version': loaderVersion - } - } -} - -compileJava.enabled = false -processResources.enabled = false \ No newline at end of file diff --git a/src/test/resources/projects/multi-mc-versions/settings.gradle b/src/test/resources/projects/multi-mc-versions/settings.gradle index a7376582..ba144ea6 100644 --- a/src/test/resources/projects/multi-mc-versions/settings.gradle +++ b/src/test/resources/projects/multi-mc-versions/settings.gradle @@ -1,3 +1,16 @@ +plugins { + id 'fabric-loom' apply false +} + +gradle.lifecycle.beforeProject { + if (it.rootProject == it) { + return + } + + apply plugin: 'fabric-loom' + apply plugin: 'loom-test-plugin' +} + rootProject.name = "multi-mc-versions" // Yes lot of mc version