Merge 1.9

This commit is contained in:
Juuz
2024-12-06 19:35:18 +02:00
48 changed files with 567 additions and 346 deletions

View File

@@ -3,9 +3,12 @@ package net.fabricmc.loom.bootstrap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.inject.Inject;
import org.gradle.api.JavaVersion; import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.configuration.BuildFeatures;
import org.gradle.api.plugins.PluginAware; import org.gradle.api.plugins.PluginAware;
import org.gradle.util.GradleVersion; 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. * 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") @SuppressWarnings("unused")
public class LoomGradlePluginBootstrap implements Plugin<PluginAware> { public abstract class LoomGradlePluginBootstrap implements Plugin<PluginAware> {
private static final String MIN_SUPPORTED_GRADLE_VERSION = "8.10"; 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_JAVA_VERSION = 17;
private static final int MIN_SUPPORTED_MAJOR_IDEA_VERSION = 2022; 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 PLUGIN_CLASS_NAME = "net.fabricmc.loom.LoomGradlePlugin";
private static final String IDEA_VERSION_PROP_KEY = "idea.version"; private static final String IDEA_VERSION_PROP_KEY = "idea.version";
@Inject
protected abstract BuildFeatures getBuildFeatures();
@Override @Override
public void apply(PluginAware pluginAware) { public void apply(PluginAware pluginAware) {
if (pluginAware instanceof Project) { if (pluginAware instanceof Project) {
Project project = (Project) pluginAware; 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(); validateEnvironment();
} else { } else {
project.getLogger().lifecycle("Loom environment validation disabled. Please re-enable before reporting any issues."); project.getLogger().lifecycle("Loom environment validation disabled. Please re-enable before reporting any issues.");

View File

@@ -7,37 +7,11 @@ plugins {
id 'groovy' id 'groovy'
id 'checkstyle' id 'checkstyle'
id 'codenarc' 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.spotless)
alias(libs.plugins.retry) 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 { tasks.withType(JavaCompile).configureEach {
it.options.encoding = "UTF-8" it.options.encoding = "UTF-8"
} }
@@ -49,7 +23,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
} }
group = "dev.architectury" group = "dev.architectury"
def baseVersion = '1.8' def baseVersion = '1.9'
def ENV = System.getenv() def ENV = System.getenv()
def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999" def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999"

View File

@@ -2,4 +2,6 @@ name = architectury-loom
description = A Gradle plugin for Fabric, Forge and Quilt modding. description = A Gradle plugin for Fabric, Forge and Quilt modding.
url = https://github.com/architectury/architectury-loom url = https://github.com/architectury/architectury-loom
kotlin.stdlib.default.dependency = false kotlin.stdlib.default.dependency = false
# Suppress a deprecation warning within the Kotlin Gradle plugin
kotlin.mpp.keepMppDependenciesIntactInPoms = true

View File

@@ -1,5 +1,5 @@
[versions] [versions]
kotlin = "1.9.24" kotlin = "2.0.20"
asm = "9.7.1" asm = "9.7.1"
commons-io = "2.15.1" commons-io = "2.15.1"
gson = "2.10.1" gson = "2.10.1"
@@ -11,7 +11,6 @@ access-widener = "2.1.0"
mapping-io = "0.6.1" mapping-io = "0.6.1"
lorenz-tiny = "4.0.2" lorenz-tiny = "4.0.2"
mercury = "0.1.4.17" mercury = "0.1.4.17"
kotlinx-metadata = "0.9.0"
loom-native = "0.2.0" loom-native = "0.2.0"
# Plugins # Plugins
@@ -52,7 +51,7 @@ fabric-loom-nativelib = { module = "net.fabricmc:fabric-loom-native", version.re
# Misc # Misc
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 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 support
forge-installer-tools = { module = "net.minecraftforge:installertools", version.ref = "forge-installer-tools" } forge-installer-tools = { module = "net.minecraftforge:installertools", version.ref = "forge-installer-tools" }

View File

@@ -1,13 +1,13 @@
[versions] [versions]
spock = "2.3-groovy-3.0" spock = "2.3-groovy-3.0"
junit = "5.11.1" junit = "5.11.3"
javalin = "6.3.0" javalin = "6.3.0"
mockito = "5.13.0" mockito = "5.14.2"
java-debug = "0.52.0" java-debug = "0.52.0"
mixin = "0.15.3+mixin.0.8.7" mixin = "0.15.3+mixin.0.8.7"
gradle-nightly = "8.12-20241009055624+0000" gradle-nightly = "8.12-20241110002642+0000"
fabric-loader = "0.16.5" fabric-loader = "0.16.9"
fabric-installer = "1.0.1" fabric-installer = "1.0.1"
[libraries] [libraries]

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -153,6 +153,8 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
boolean isConfigurationCacheActive(); boolean isConfigurationCacheActive();
boolean isProjectIsolationActive();
// =================== // ===================
// Architectury Loom // Architectury Loom
// =================== // ===================

View File

@@ -28,6 +28,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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()); 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<File> sortedJars = jars.stream().sorted(Comparator.comparing(File::getName)).toList();
try { try {
ZipUtils.add(modJar.toPath(), jars.stream().map(file -> { ZipUtils.add(modJar.toPath(), sortedJars.stream().map(file -> {
try { try {
return new Pair<>("META-INF/jars/" + file.getName(), Files.readAllBytes(file.toPath())); return new Pair<>("META-INF/jars/" + file.getName(), Files.readAllBytes(file.toPath()));
} catch (IOException e) { } catch (IOException e) {
@@ -75,7 +79,7 @@ public class JarNester {
nestedJars = new JsonArray(); nestedJars = new JsonArray();
} }
for (File file : jars) { for (File file : sortedJars) {
String nestedJarPath = "META-INF/jars/" + file.getName(); String nestedJarPath = "META-INF/jars/" + file.getName();
Preconditions.checkArgument(FabricModJsonFactory.isNestableModJar(file, platform), "Cannot nest none mod jar: " + file.getName()); Preconditions.checkArgument(FabricModJsonFactory.isNestableModJar(file, platform), "Cannot nest none mod jar: " + file.getName());

View File

@@ -26,6 +26,7 @@ package net.fabricmc.loom.configuration;
import static net.fabricmc.loom.util.Constants.Configurations; import static net.fabricmc.loom.util.Constants.Configurations;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -34,6 +35,7 @@ import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.inject.Inject; 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.TaskContainer;
import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.api.tasks.javadoc.Javadoc;
import org.gradle.api.tasks.testing.Test;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.InterfaceInjectionExtensionAPI; 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.ProcessUtil;
import net.fabricmc.loom.util.gradle.GradleUtils; import net.fabricmc.loom.util.gradle.GradleUtils;
import net.fabricmc.loom.util.gradle.SourceSetHelper; 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.ScopedServiceFactory;
import net.fabricmc.loom.util.service.ServiceFactory; import net.fabricmc.loom.util.service.ServiceFactory;
@@ -129,7 +133,7 @@ public abstract class CompileConfiguration implements Runnable {
extension.setDependencyManager(dependencyManager); extension.setDependencyManager(dependencyManager);
dependencyManager.handleDependencies(getProject(), serviceFactory); dependencyManager.handleDependencies(getProject(), serviceFactory);
} catch (Exception e) { } catch (Exception e) {
ExceptionUtil.processException(e, getProject()); ExceptionUtil.processException(e, DaemonUtils.Context.fromProject(getProject()));
disownLock(); disownLock();
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e); throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to setup Minecraft", e);
} }
@@ -144,6 +148,7 @@ public abstract class CompileConfiguration implements Runnable {
} }
configureDecompileTasks(configContext); configureDecompileTasks(configContext);
configureTestTask();
if (extension.isForgeLike()) { if (extension.isForgeLike()) {
if (extension.isDataGenEnabled()) { if (extension.isDataGenEnabled()) {
@@ -335,6 +340,26 @@ public abstract class CompileConfiguration implements Runnable {
.afterEvaluation(); .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() { private LockFile getLockFile() {
final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); final LoomGradleExtension extension = LoomGradleExtension.get(getProject());
final Path cacheDirectory = extension.getFiles().getUserCache().toPath(); final Path cacheDirectory = extension.getFiles().getUserCache().toPath();

View File

@@ -44,17 +44,16 @@ import org.gradle.api.artifacts.Dependency;
import org.gradle.api.file.RegularFileProperty; import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Delete;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskContainer;
import org.gradle.jvm.tasks.Jar; import org.gradle.jvm.tasks.Jar;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import net.fabricmc.loom.LoomGradleExtension; 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.download.DownloadException;
import net.fabricmc.loom.util.fmj.FabricModJson; import net.fabricmc.loom.util.fmj.FabricModJson;
import net.fabricmc.loom.util.fmj.FabricModJsonFactory; import net.fabricmc.loom.util.fmj.FabricModJsonFactory;
@@ -112,6 +111,7 @@ public abstract class FabricApiExtension {
settings.getCreateSourceSet().convention(false); settings.getCreateSourceSet().convention(false);
settings.getStrictValidation().convention(false); settings.getStrictValidation().convention(false);
settings.getAddToResources().convention(true); settings.getAddToResources().convention(true);
settings.getClient().convention(false);
action.execute(settings); action.execute(settings);
@@ -133,33 +133,23 @@ public abstract class FabricApiExtension {
jar.exclude(".cache/**"); jar.exclude(".cache/**");
}); });
taskContainer.getByName(LifecycleBasePlugin.CLEAN_TASK_NAME, task -> {
Delete clean = (Delete) task;
clean.delete(outputDirectory);
});
if (settings.getCreateSourceSet().get()) { if (settings.getCreateSourceSet().get()) {
final boolean isClientAndSplit = extension.areEnvironmentSourceSetsSplit() && settings.getClient().get();
SourceSetContainer sourceSets = SourceSetHelper.getSourceSets(getProject()); 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 dataGenSourceSet = sourceSets.create(DATAGEN_SOURCESET_NAME, sourceSet -> {
sourceSet.setCompileClasspath( dependsOn(sourceSet, mainSourceSet);
sourceSet.getCompileClasspath()
.plus(mainSourceSet.getOutput())
);
sourceSet.setRuntimeClasspath( if (isClientAndSplit) {
sourceSet.getRuntimeClasspath() dependsOn(sourceSet, SourceSetHelper.getSourceSetByName(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME, getProject()));
.plus(mainSourceSet.getOutput()) }
);
extendsFrom(getProject(), sourceSet.getCompileClasspathConfigurationName(), mainSourceSet.getCompileClasspathConfigurationName());
extendsFrom(getProject(), sourceSet.getRuntimeClasspathConfigurationName(), mainSourceSet.getRuntimeClasspathConfigurationName());
}); });
settings.getModId().convention(getProject().provider(() -> { settings.getModId().convention(getProject().provider(() -> {
try { try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(dataGenSourceSet); final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), dataGenSourceSet);
if (fabricModJson == null) { 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()"); 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()) { if (settings.getCreateRunConfiguration().get()) {
extension.getRunConfigs().create("datagen", run -> { 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.setConfigName("Data Generation");
run.property("fabric-api.datagen"); run.property("fabric-api.datagen");
@@ -200,6 +190,11 @@ public abstract class FabricApiExtension {
run.source(DATAGEN_SOURCESET_NAME); 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. * Contains a boolean property indicating whether the generated resources will be automatically added to the main sourceset.
*/ */
Property<Boolean> getAddToResources(); Property<Boolean> getAddToResources();
/**
* Contains a boolean property indicating whether data generation will be compiled and ran with the client.
*/
Property<Boolean> getClient();
} }
private String getDependencyNotation(String moduleName, String fabricApiVersion) { private String getDependencyNotation(String moduleName, String fabricApiVersion) {
@@ -326,4 +326,19 @@ public abstract class FabricApiExtension {
configuration.extendsFrom(configurations.getByName(extendsFrom)); 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());
}
} }

View File

@@ -84,7 +84,8 @@ public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricMo
} }
} }
if (!GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS)) { // TODO provide a project isolated way of doing this.
if (!extension.isProjectIsolationActive() && !GradleUtils.getBooleanProperty(project, Constants.Properties.DISABLE_PROJECT_DEPENDENT_MODS)) {
// Add all the dependent projects // Add all the dependent projects
for (Project dependentProject : getDependentProjects(project).toList()) { for (Project dependentProject : getDependentProjects(project).toList()) {
mods.addAll(fmjCache.computeIfAbsent(dependentProject.getPath(), $ -> { mods.addAll(fmjCache.computeIfAbsent(dependentProject.getPath(), $ -> {
@@ -97,8 +98,8 @@ public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricMo
} }
private static Stream<Project> getDependentProjects(Project project) { private static Stream<Project> getDependentProjects(Project project) {
final Stream<Project> runtimeProjects = getLoomProjectDependencies(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); final Stream<Project> runtimeProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
final Stream<Project> compileProjects = getLoomProjectDependencies(project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)); final Stream<Project> compileProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
return Stream.concat(runtimeProjects, compileProjects) return Stream.concat(runtimeProjects, compileProjects)
.distinct(); .distinct();
@@ -154,19 +155,19 @@ public record SpecContextImpl(List<FabricModJson> modDependencies, List<FabricMo
// Returns a list of Loom Projects found in both the runtime and compile classpath // Returns a list of Loom Projects found in both the runtime and compile classpath
private static Stream<Project> getCompileRuntimeProjectDependencies(Project project) { private static Stream<Project> getCompileRuntimeProjectDependencies(Project project) {
final Stream<Project> runtimeProjects = getLoomProjectDependencies(project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME)); final Stream<Project> runtimeProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME));
final List<Project> compileProjects = getLoomProjectDependencies(project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)).toList(); final List<Project> compileProjects = getLoomProjectDependencies(project, project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME)).toList();
return runtimeProjects return runtimeProjects
.filter(compileProjects::contains); // Use the intersection of the two configurations. .filter(compileProjects::contains); // Use the intersection of the two configurations.
} }
// Returns a list of Loom Projects found in the provided Configuration // Returns a list of Loom Projects found in the provided Configuration
private static Stream<Project> getLoomProjectDependencies(Configuration configuration) { private static Stream<Project> getLoomProjectDependencies(Project project, Configuration configuration) {
return configuration.getAllDependencies() return configuration.getAllDependencies()
.withType(ProjectDependency.class) .withType(ProjectDependency.class)
.stream() .stream()
.map(GradleUtils::getDependencyProject) .map((d) -> project.project(d.getPath()))
.filter(GradleUtils::isLoomProject); .filter(GradleUtils::isLoomProject);
} }

View File

@@ -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.configuration.providers.minecraft.library.processors.RuntimeLog4jLibraryProcessor;
import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.Platform; import net.fabricmc.loom.util.Platform;
import net.fabricmc.loom.util.gradle.GradleUtils;
public class MinecraftLibraryProvider { public class MinecraftLibraryProvider {
private static final Platform platform = Platform.CURRENT; private static final Platform platform = Platform.CURRENT;
@@ -124,7 +125,7 @@ public class MinecraftLibraryProvider {
} }
private JavaVersion getTargetRuntimeJavaVersion() { 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) { if (property != null) {
// This is very much a last ditch effort to allow users to set the runtime java version // This is very much a last ditch effort to allow users to set the runtime java version

View File

@@ -59,6 +59,13 @@ public abstract class SandboxConfiguration implements Runnable {
@Override @Override
public void run() { 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) { if (getProject().findProperty(Constants.Properties.SANDBOX) == null) {
LOGGER.debug("No fabric sandbox property set"); LOGGER.debug("No fabric sandbox property set");
return; return;

View File

@@ -342,7 +342,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA
@Override @Override
public String getModVersion() { public String getModVersion() {
try { try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(SourceSetHelper.getMainSourceSet(getProject())); final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(getProject(), SourceSetHelper.getMainSourceSet(getProject()));
if (fabricModJson == null) { if (fabricModJson == null) {
throw new RuntimeException("Could not find a fabric.mod.json file in the main sourceset"); throw new RuntimeException("Could not find a fabric.mod.json file in the main sourceset");

View File

@@ -131,6 +131,10 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
if (refreshDeps) { if (refreshDeps) {
project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower."); 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 @Override
@@ -339,6 +343,11 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl
return configurationCacheActive; return configurationCacheActive;
} }
@Override
public boolean isProjectIsolationActive() {
return isolatedProjectsActive;
}
@Override @Override
public ForgeExtensionAPI getForge() { public ForgeExtensionAPI getForge() {
ModPlatform.assertPlatform(this, ModPlatform.FORGE); ModPlatform.assertPlatform(this, ModPlatform.FORGE);

View File

@@ -126,6 +126,11 @@ public abstract class AbstractRemapJarTask extends Jar {
usesService(jarManifestServiceProvider); usesService(jarManifestServiceProvider);
} }
@Override
protected void copy() {
// Skip the default copy behaviour of AbstractCopyTask.
}
public final <P extends AbstractRemapParams> void submitWork(Class<? extends AbstractRemapAction<P>> workAction, Action<P> action) { public final <P extends AbstractRemapParams> void submitWork(Class<? extends AbstractRemapAction<P>> workAction, Action<P> action) {
final WorkQueue workQueue = getWorkerExecutor().noIsolation(); final WorkQueue workQueue = getWorkerExecutor().noIsolation();

View File

@@ -31,6 +31,7 @@ import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function; 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.MapProperty;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider; import org.gradle.api.provider.Provider;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.specs.Spec; import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.InputFiles;
@@ -52,9 +52,9 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.configuration.ide.RunConfig; import net.fabricmc.loom.configuration.ide.RunConfig;
import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.gradle.SyncTaskBuildService;
public abstract class AbstractRunTask extends JavaExec { public abstract class AbstractRunTask extends JavaExec {
private static final CharsetEncoder ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder(); private static final CharsetEncoder ASCII_ENCODER = StandardCharsets.US_ASCII.newEncoder();
@@ -70,15 +70,14 @@ public abstract class AbstractRunTask extends JavaExec {
protected abstract Property<Boolean> getUseArgFile(); protected abstract Property<Boolean> getUseArgFile();
@Input @Input
protected abstract Property<String> getProjectDir(); protected abstract Property<String> 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<String> 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 // 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 @InputFiles
protected abstract ConfigurableFileCollection getInternalClasspath(); protected abstract ConfigurableFileCollection getInternalClasspath();
// Prevent Gradle from running two run tasks in parallel
@ServiceReference(SyncTaskBuildService.NAME)
abstract Property<SyncTaskBuildService> getSyncTask();
public AbstractRunTask(Function<Project, RunConfig> configProvider) { public AbstractRunTask(Function<Project, RunConfig> configProvider) {
super(); super();
setGroup(Constants.TaskGroup.FABRIC); setGroup(Constants.TaskGroup.FABRIC);
@@ -100,6 +99,10 @@ public abstract class AbstractRunTask extends JavaExec {
getInternalJvmArgs().set(config.map(runConfig -> runConfig.vmArgs)); getInternalJvmArgs().set(config.map(runConfig -> runConfig.vmArgs));
getUseArgFile().set(getProject().provider(this::canUseArgFile)); getUseArgFile().set(getProject().provider(this::canUseArgFile));
getProjectDir().set(getProject().getProjectDir().getAbsolutePath()); 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() { private boolean canUseArgFile() {
@@ -154,7 +157,8 @@ public abstract class AbstractRunTask extends JavaExec {
.collect(Collectors.joining(File.pathSeparator)); .collect(Collectors.joining(File.pathSeparator));
try { 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); Files.writeString(argsFile, content, StandardCharsets.UTF_8);
args.add("@" + argsFile.toAbsolutePath()); args.add("@" + argsFile.toAbsolutePath());
} catch (IOException e) { } catch (IOException e) {

View File

@@ -97,10 +97,12 @@ import net.fabricmc.loom.util.ExceptionUtil;
import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.FileSystemUtil;
import net.fabricmc.loom.util.IOStringConsumer; import net.fabricmc.loom.util.IOStringConsumer;
import net.fabricmc.loom.util.Platform; 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.SyncTaskBuildService;
import net.fabricmc.loom.util.gradle.ThreadedProgressLoggerConsumer; import net.fabricmc.loom.util.gradle.ThreadedProgressLoggerConsumer;
import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger; import net.fabricmc.loom.util.gradle.ThreadedSimpleProgressLogger;
import net.fabricmc.loom.util.gradle.WorkerDaemonClientsManagerHelper; 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.IPCClient;
import net.fabricmc.loom.util.ipc.IPCServer; import net.fabricmc.loom.util.ipc.IPCServer;
import net.fabricmc.loom.util.service.ScopedServiceFactory; import net.fabricmc.loom.util.service.ScopedServiceFactory;
@@ -178,6 +180,14 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
@Internal @Internal
protected abstract RegularFileProperty getDecompileCacheFile(); protected abstract RegularFileProperty getDecompileCacheFile();
@ApiStatus.Internal
@Input
protected abstract Property<Integer> getMaxCachedFiles();
@ApiStatus.Internal
@Input
protected abstract Property<Integer> getMaxCacheFileAge();
// Injects // Injects
@Inject @Inject
protected abstract WorkerExecutor getWorkerExecutor(); protected abstract WorkerExecutor getWorkerExecutor();
@@ -191,6 +201,9 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
@Inject @Inject
protected abstract ProgressLoggerFactory getProgressLoggerFactory(); protected abstract ProgressLoggerFactory getProgressLoggerFactory();
@Nested
protected abstract Property<DaemonUtils.Context> getDaemonUtilsContext();
// Prevent Gradle from running two gen sources tasks in parallel // Prevent Gradle from running two gen sources tasks in parallel
@ServiceReference(SyncTaskBuildService.NAME) @ServiceReference(SyncTaskBuildService.NAME)
abstract Property<SyncTaskBuildService> getSyncTask(); abstract Property<SyncTaskBuildService> getSyncTask();
@@ -242,6 +255,11 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
getMappings().set(SourceMappingsService.create(getProject())); 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)); mustRunAfter(getProject().getTasks().withType(AbstractRemapJarTask.class));
} }
@@ -259,7 +277,7 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask {
try (var timer = new Timer("Decompiled sources")) { try (var timer = new Timer("Decompiled sources")) {
runWithoutCache(); runWithoutCache();
} catch (Exception e) { } catch (Exception e) {
ExceptionUtil.processException(e, getProject()); ExceptionUtil.processException(e, getDaemonUtilsContext().get());
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e); 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 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()); 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)) { try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) {
runWithCache(fs.getRoot()); runWithCache(fs.getRoot());
} }
} catch (Exception e) { } catch (Exception e) {
ExceptionUtil.processException(e, getProject()); ExceptionUtil.processException(e, getDaemonUtilsContext().get());
throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e); 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 classesInputJar = getClassesInputJar().getSingleFile().toPath();
final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath(); final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath();
final Path classesOutputJar = getClassesOutputJar().getSingleFile().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 var decompileCache = new CachedFileStoreImpl<>(cacheRoot, CachedData.SERIALIZER, cacheRules);
final String cacheKey = getCacheKey(); final String cacheKey = getCacheKey();
final CachedJarProcessor cachedJarProcessor = new CachedJarProcessor(decompileCache, cacheKey); final CachedJarProcessor cachedJarProcessor = new CachedJarProcessor(decompileCache, cacheKey);
final CachedJarProcessor.WorkRequest workRequest; final CachedJarProcessor.WorkRequest workRequest;
getLogger().info("Decompile cache key: {}", cacheKey); getLogger().info("Decompile cache key: {}", cacheKey);
getLogger().debug("Decompile cache rules: {}", cacheRules);
try (var timer = new Timer("Prepare job")) { try (var timer = new Timer("Prepare job")) {
workRequest = cachedJarProcessor.prepareJob(classesInputJar); workRequest = cachedJarProcessor.prepareJob(classesInputJar);

View File

@@ -142,7 +142,8 @@ public abstract class RemapTaskConfiguration implements Runnable {
final Jar jarTask = (Jar) getTasks().getByName(JavaPlugin.JAR_TASK_NAME); final Jar jarTask = (Jar) getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
configuration.getArtifacts().removeIf(artifact -> { configuration.getArtifacts().removeIf(artifact -> {
// if the artifact is built by the jar task, and has the same output path. // 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));
}); });
} }
}); });

View File

@@ -59,8 +59,6 @@ import net.fabricmc.tinyremapper.IMappingProvider;
public class MixinAPMappingService extends Service<MixinAPMappingService.Options> { public class MixinAPMappingService extends Service<MixinAPMappingService.Options> {
public static final ServiceType<Options, MixinAPMappingService> TYPE = new ServiceType<>(Options.class, MixinAPMappingService.class); public static final ServiceType<Options, MixinAPMappingService> 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. // Again look into what the result of changing this would be.
private static final boolean USE_ALL_SOURCE_SETS = true; private static final boolean USE_ALL_SOURCE_SETS = true;
private static final Logger LOGGER = LoggerFactory.getLogger(MixinAPMappingService.class); private static final Logger LOGGER = LoggerFactory.getLogger(MixinAPMappingService.class);
@@ -105,7 +103,8 @@ public class MixinAPMappingService extends Service<MixinAPMappingService.Options
} }
}; };
if (!INCLUDE_CROSS_PROJECT_MAPPINGS) { if (thisExtension.isProjectIsolationActive()) {
// TODO provide a project isolated way of remapping with dependency mixin mapping
processProject.accept(thisProject); processProject.accept(thisProject);
} else { } else {
GradleUtils.allLoomProjects(thisProject.getGradle(), project -> { GradleUtils.allLoomProjects(thisProject.getGradle(), project -> {

View File

@@ -111,7 +111,7 @@ public class TinyRemapperService extends Service<TinyRemapperService.Options> im
options.getUselegacyMixinAP().set(legacyMixin); options.getUselegacyMixinAP().set(legacyMixin);
options.getKotlinClasspathService().set(KotlinClasspathService.createOptions(project)); options.getKotlinClasspathService().set(KotlinClasspathService.createOptions(project));
options.getClasspath().from(classpath); options.getClasspath().from(classpath);
options.getKnownIndyBsms().set(extension.getKnownIndyBsms()); options.getKnownIndyBsms().set(extension.getKnownIndyBsms().get().stream().sorted().toList());
options.getRemapperExtensions().set(extension.getRemapperExtensions()); options.getRemapperExtensions().set(extension.getRemapperExtensions());
}); });
} }

View File

@@ -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. * 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 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 ALLOW_MISMATCHED_PLATFORM_VERSION = "loom.allowMismatchedPlatformVersion";
public static final String IGNORE_DEPENDENCY_LOOM_VERSION_VALIDATION = "loom.ignoreDependencyLoomVersionValidation"; public static final String IGNORE_DEPENDENCY_LOOM_VERSION_VALIDATION = "loom.ignoreDependencyLoomVersionValidation";
} }

View File

@@ -31,7 +31,6 @@ import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.gradle.api.Project;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -60,7 +59,7 @@ public final class ExceptionUtil {
return constructor.apply(descriptiveMessage, cause); 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; Throwable cause = e;
boolean unrecoverable = false; boolean unrecoverable = false;
@@ -68,7 +67,7 @@ public final class ExceptionUtil {
if (cause instanceof FileSystemUtil.UnrecoverableZipException) { if (cause instanceof FileSystemUtil.UnrecoverableZipException) {
unrecoverable = true; unrecoverable = true;
} else if (cause instanceof FileSystemException fse) { } else if (cause instanceof FileSystemException fse) {
printFileLocks(fse.getFile(), project); printFileLocks(fse.getFile());
break; break;
} }
@@ -76,11 +75,11 @@ public final class ExceptionUtil {
} }
if (unrecoverable) { 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); final Path path = Paths.get(filename);
if (!Files.exists(path)) { if (!Files.exists(path)) {
@@ -100,13 +99,13 @@ public final class ExceptionUtil {
return; 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"; 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) { for (ProcessHandle process : processes) {
project.getLogger().error(processUtil.printWithParents(process)); LOGGER.error(processUtil.printWithParents(process));
} }
} }
} }

View File

@@ -28,7 +28,7 @@ import java.util.List;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.gson.Gson; import com.google.gson.Gson;
import kotlinx.metadata.jvm.KotlinClassMetadata; import kotlin.metadata.jvm.KotlinClassMetadata;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.commons.ClassRemapper;

View File

@@ -37,18 +37,22 @@ import org.slf4j.LoggerFactory;
import net.fabricmc.loom.nativeplatform.LoomNativePlatform; import net.fabricmc.loom.nativeplatform.LoomNativePlatform;
import net.fabricmc.loom.nativeplatform.LoomNativePlatformException; 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 String EXPLORER_COMMAND = "C:\\Windows\\explorer.exe";
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessUtil.class); private static final Logger LOGGER = LoggerFactory.getLogger(ProcessUtil.class);
public static ProcessUtil create(Project project) { 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) { public String printWithParents(ProcessHandle handle) {
String result = printWithParents(handle, 0).trim(); 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; 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<String> getProcessArguments(ProcessHandle handle) { private Optional<String> getProcessArguments(ProcessHandle handle) {
if (logLevel != LogLevel.INFO && logLevel != LogLevel.DEBUG) { if (argumentVisibility != ArgumentVisibility.SHOW_SENSITIVE) {
return Optional.empty(); return Optional.empty();
} }
@@ -117,4 +121,14 @@ public record ProcessUtil(LogLevel logLevel) {
return Optional.of(joiner.toString()); 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;
}
}
} }

View File

@@ -39,6 +39,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import dev.architectury.loom.metadata.ModMetadataFile; import dev.architectury.loom.metadata.ModMetadataFile;
import dev.architectury.loom.metadata.ModMetadataFiles; import dev.architectury.loom.metadata.ModMetadataFiles;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting; import org.jetbrains.annotations.VisibleForTesting;
@@ -134,8 +135,8 @@ public final class FabricModJsonFactory {
} }
@Nullable @Nullable
public static FabricModJson createFromSourceSetsNullable(SourceSet... sourceSets) throws IOException { public static FabricModJson createFromSourceSetsNullable(Project project, SourceSet... sourceSets) throws IOException {
final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, sourceSets); final File file = SourceSetHelper.findFirstFileInResource(FABRIC_MOD_JSON, project, sourceSets);
if (file == null) { if (file == null) {
// Try another mod metadata file if fabric.mod.json wasn't found. // 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)) { 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) { } catch (JsonSyntaxException e) {
LOGGER.warn("Failed to parse fabric.mod.json: {}", file.getAbsolutePath()); LOGGER.warn("Failed to parse fabric.mod.json: {}", file.getAbsolutePath());
return null; return null;

View File

@@ -48,7 +48,7 @@ public class FabricModJsonHelpers {
} }
try { try {
final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(sourceSets.toArray(SourceSet[]::new)); final FabricModJson fabricModJson = FabricModJsonFactory.createFromSourceSetsNullable(project, sourceSets.toArray(SourceSet[]::new));
if (fabricModJson != null) { if (fabricModJson != null) {
return List.of(fabricModJson); return List.of(fabricModJson);

View File

@@ -30,6 +30,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import net.fabricmc.loom.util.ZipUtils; 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 @Override
public byte[] read(String path) throws IOException { public byte[] read(String path) throws IOException {
return Files.readAllBytes(findFile(path).toPath()); return Files.readAllBytes(findFile(path).toPath());
} }
private File findFile(String path) throws IOException { 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) { if (file == null) {
throw new FileNotFoundException("Could not find: " + path); throw new FileNotFoundException("Could not find: " + path);

View File

@@ -25,24 +25,18 @@
package net.fabricmc.loom.util.gradle; package net.fabricmc.loom.util.gradle;
import java.io.File; import java.io.File;
import java.lang.reflect.Field;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.file.RegularFileProperty; 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.invocation.Gradle;
import org.gradle.api.provider.Provider; 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; import net.fabricmc.loom.util.Constants;
public final class GradleUtils { public final class GradleUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(GradleUtils.class);
private GradleUtils() { private GradleUtils() {
} }
@@ -71,6 +65,13 @@ public final class GradleUtils {
} }
public static Provider<Boolean> getBooleanPropertyProvider(Project project, String key) { public static Provider<Boolean> 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 // Works around https://github.com/gradle/gradle/issues/23572
return project.provider(() -> { return project.provider(() -> {
final Object value = project.findProperty(key); final Object value = project.findProperty(key);
@@ -87,10 +88,37 @@ public final class GradleUtils {
}); });
} }
public static Provider<Integer> 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) { public static boolean getBooleanProperty(Project project, String key) {
return getBooleanPropertyProvider(project, key).getOrElse(false); 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 // A hack to include the given file in the configuration cache input
// this ensures that configuration cache is invalidated when the file changes // this ensures that configuration cache is invalidated when the file changes
public static File configurationInputFile(Project project, File file) { public static File configurationInputFile(Project project, File file) {
@@ -98,33 +126,4 @@ public final class GradleUtils {
property.set(file); property.set(file);
return property.getAsFile().get(); 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<DefaultProjectDependency> 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<DelegatingProjectDependency> 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;
}
} }

View File

@@ -268,11 +268,11 @@ public final class SourceSetHelper {
} }
@Nullable @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(sourceSet);
Objects.requireNonNull(path); Objects.requireNonNull(path);
final Project project = getSourceSetProject(sourceSet);
final LoomGradleExtension extension = LoomGradleExtension.get(project); final LoomGradleExtension extension = LoomGradleExtension.get(project);
if (extension.isConfigurationCacheActive()) { if (extension.isConfigurationCacheActive()) {
@@ -298,9 +298,9 @@ public final class SourceSetHelper {
} }
@Nullable @Nullable
public static File findFirstFileInResource(String path, SourceSet... sourceSets) { public static File findFirstFileInResource(String path, Project project, SourceSet... sourceSets) {
for (SourceSet sourceSet : sourceSets) { for (SourceSet sourceSet : sourceSets) {
File file = findFileInResource(sourceSet, path); File file = findFileInResource(project, sourceSet, path);
if (file != null) { if (file != null) {
return file; return file;

View File

@@ -32,7 +32,6 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.gradle.api.Transformer; import org.gradle.api.Transformer;
import org.gradle.process.internal.JvmOptions;
import org.gradle.workers.internal.DaemonForkOptions; import org.gradle.workers.internal.DaemonForkOptions;
import org.gradle.workers.internal.WorkerDaemonClientsManager; import org.gradle.workers.internal.WorkerDaemonClientsManager;
@@ -88,8 +87,11 @@ public class WorkerDaemonClientsManagerHelper {
try { try {
Method getJvmOptions = forkOptions.getClass().getDeclaredMethod("getJvmOptions"); Method getJvmOptions = forkOptions.getClass().getDeclaredMethod("getJvmOptions");
getJvmOptions.setAccessible(true); getJvmOptions.setAccessible(true);
JvmOptions jvmOptions = (JvmOptions) getJvmOptions.invoke(forkOptions); Object jvmOptions = getJvmOptions.invoke(forkOptions);
return jvmOptions.getMutableSystemProperties(); Method getMutableSystemProperties = jvmOptions.getClass().getDeclaredMethod("getMutableSystemProperties");
getMutableSystemProperties.setAccessible(true);
//noinspection unchecked
return (Map<String, Object>) getMutableSystemProperties.invoke(jvmOptions);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException("Failed to daemon system properties", e); throw new RuntimeException("Failed to daemon system properties", e);
} }

View File

@@ -28,14 +28,16 @@ import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import javax.inject.Inject;
import org.gradle.api.Project; 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.cache.FileLockManager;
import org.gradle.internal.file.Chmod; import org.gradle.internal.file.Chmod;
import org.gradle.internal.remote.internal.RemoteConnection; import org.gradle.internal.remote.internal.RemoteConnection;
import org.gradle.internal.remote.internal.inet.TcpOutgoingConnector; import org.gradle.internal.remote.internal.inet.TcpOutgoingConnector;
import org.gradle.internal.serialize.Serializers; 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.DaemonClientConnection;
import org.gradle.launcher.daemon.client.StopDispatcher; import org.gradle.launcher.daemon.client.StopDispatcher;
import org.gradle.launcher.daemon.protocol.DaemonMessageSerializer; 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. * Request the Gradle daemon to stop when it becomes idle.
*/ */
public static void tryStopGradleDaemon(Project project) { public static void tryStopGradleDaemon(DaemonUtils.Context context) {
try { try {
stopWhenIdle(project); stopWhenIdle(context);
} catch (Throwable t) { } catch (Throwable t) {
LOGGER.error("Failed to request the Gradle demon to stop", t); LOGGER.error("Failed to request the Gradle demon to stop", t);
} }
} }
@VisibleForTesting @VisibleForTesting
public static boolean stopWhenIdle(Project project) { public static boolean stopWhenIdle(DaemonUtils.Context context) {
DaemonInfo daemonInfo = findCurrentDaemon(project); DaemonInfo daemonInfo = findCurrentDaemon(context);
if (daemonInfo == null) { if (daemonInfo == null) {
return false; return false;
@@ -98,14 +100,13 @@ public final class DaemonUtils {
} }
@Nullable @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. // 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"); final Path registryBin = Path.of(context.getRegistryBin().get());
project.getLogger().lifecycle("Looking for daemon in: " + registryBin); LOGGER.info("Looking for daemon in: {}", registryBin);
// We can use a PersistentDaemonRegistry to read this // We can use a PersistentDaemonRegistry to read this
final ServiceRegistry services = ((DefaultGradle) project.getGradle()).getServices(); final DaemonRegistry registry = new PersistentDaemonRegistry(registryBin.toFile(), context.getFileLockManager(), context.getChmod());
final DaemonRegistry registry = new PersistentDaemonRegistry(registryBin.toFile(), services.get(FileLockManager.class), services.get(Chmod.class));
final long pid = ProcessHandle.current().pid(); final long pid = ProcessHandle.current().pid();
final List<DaemonInfo> runningDaemons = registry.getAll(); final List<DaemonInfo> runningDaemons = registry.getAll();
@@ -121,4 +122,33 @@ public final class DaemonUtils {
LOGGER.warn("Could not find current process in daemon registry: {}", registryBin); LOGGER.warn("Could not find current process in daemon registry: {}", registryBin);
return null; return null;
} }
public abstract static class Context {
@Input
protected abstract Property<String> 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();
}
}
} }

View File

@@ -61,17 +61,16 @@ public final class KotlinClasspathService extends Service<KotlinClasspathService
return createOptions( return createOptions(
project, project,
KotlinPluginUtils.getKotlinPluginVersion(project), KotlinPluginUtils.getKotlinPluginVersion(project)
KotlinPluginUtils.getKotlinMetadataVersion()
); );
} }
private static Provider<Options> createOptions(Project project, String kotlinVersion, String kotlinMetadataVersion) { private static Provider<Options> createOptions(Project project, String kotlinVersion) {
// Create a detached config to resolve the kotlin std lib for the provided version. // Create a detached config to resolve the kotlin std lib for the provided version.
Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration( Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration(
project.getDependencies().create("org.jetbrains.kotlin:kotlin-stdlib:" + kotlinVersion), 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 // 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 -> { return TYPE.create(project, options -> {

View File

@@ -29,7 +29,6 @@ import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.Properties; import java.util.Properties;
import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.gradle.api.Project; import org.gradle.api.Project;
public class KotlinPluginUtils { public class KotlinPluginUtils {
@@ -56,8 +55,4 @@ public class KotlinPluginUtils {
return props.getProperty(property); return props.getProperty(property);
} }
public static String getKotlinMetadataVersion() {
return KotlinClassMetadata.class.getPackage().getImplementationVersion().split("-")[0];
}
} }

View File

@@ -24,13 +24,13 @@
package net.fabricmc.loom.kotlin.remapping 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.AnnotationVisitor
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.commons.Remapper import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.tree.AnnotationNode import org.objectweb.asm.tree.AnnotationNode
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import kotlin.metadata.jvm.KotlinClassMetadata
import kotlin.metadata.jvm.Metadata
class KotlinClassMetadataRemappingAnnotationVisitor( class KotlinClassMetadataRemappingAnnotationVisitor(
private val remapper: Remapper, private val remapper: Remapper,
@@ -62,8 +62,14 @@ class KotlinClassMetadataRemappingAnnotationVisitor(
"is using (${KotlinVersion.CURRENT}).", "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 -> { is KotlinClassMetadata.Class -> {
var klass = metadata.kmClass var klass = metadata.kmClass
klass = KotlinClassRemapper(remapper).remap(klass) klass = KotlinClassRemapper(remapper).remap(klass)

View File

@@ -24,35 +24,35 @@
package net.fabricmc.loom.kotlin.remapping 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 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) @OptIn(ExperimentalContextReceivers::class)
class KotlinClassRemapper(private val remapper: Remapper) { class KotlinClassRemapper(private val remapper: Remapper) {

View File

@@ -29,10 +29,20 @@ import spock.lang.Unroll
import net.fabricmc.loom.test.util.GradleProjectTestTrait 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 net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
class DataGenerationTest extends Specification implements GradleProjectTestTrait { 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 @Unroll
def "dataGeneration (gradle #version)"() { def "dataGeneration (gradle #version)"() {
setup: setup:
@@ -41,14 +51,7 @@ class DataGenerationTest extends Specification implements GradleProjectTestTrait
fabricApi { fabricApi {
configureDataGeneration() configureDataGeneration()
} }
''' + 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"
}
'''
when: when:
def result = gradle.run(task: "runDatagen") 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 + "%%") println("%%" + loom.runs.datagen.configName + "%%")
''' ''' + DEPENDENCIES
when: when:
def result = gradle.run(task: "runDatagen") def result = gradle.run(task: "runDatagen")
@@ -101,4 +95,111 @@ class DataGenerationTest extends Specification implements GradleProjectTestTrait
where: where:
version << STANDARD_TEST_VERSIONS 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
}
} }

View File

@@ -44,7 +44,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
setup: setup:
def gradle = gradleProject( def gradle = gradleProject(
repo: "https://github.com/FabricMC/fabric.git", repo: "https://github.com/FabricMC/fabric.git",
commit: "41bc64cd617f03d49ecc4a4f7788cb65d465415c", commit: "70277babddfaf52ee30013af94764da19473b3b1",
version: version, version: version,
patch: "fabric_api" 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('id "fabric-loom" version "1.6.11"', 'id "dev.architectury.loom"')
.replace('"fabric-loom"', '"dev.architectury.loom"') + mixinApPatch .replace('"fabric-loom"', '"dev.architectury.loom"') + mixinApPatch
def minecraftVersion = "1.21" def minecraftVersion = "1.21.4-pre3"
def server = ServerRunner.create(gradle.projectDir, minecraftVersion) def server = ServerRunner.create(gradle.projectDir, minecraftVersion)
.withMod(gradle.getOutputFile("fabric-api-999.0.0.jar")) .withMod(gradle.getOutputFile("fabric-api-999.0.0.jar"))
@@ -80,7 +80,7 @@ class FabricAPITest extends Specification implements GradleProjectTestTrait {
dependencies { dependencies {
minecraft "com.mojang:minecraft:${minecraftVersion}" 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" modImplementation "net.fabricmc.fabric-api:fabric-api:999.0.0"
} }

View File

@@ -51,6 +51,7 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait
def "build (gradle #version)"() { def "build (gradle #version)"() {
setup: setup:
def gradle = gradleProject(project: "multi-mc-versions", version: version) def gradle = gradleProject(project: "multi-mc-versions", version: version)
gradle.buildSrc("multiMcVersions", false)
versions.forEach { versions.forEach {
// Make dir as its now required by Gradle // Make dir as its now required by Gradle
@@ -58,10 +59,9 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait
} }
when: when:
def result = gradle.run(tasks: "build") def result = gradle.run(tasks: "build", isloatedProjects: true, configureOnDemand: true)
then: then:
result.task(":build").outcome == SUCCESS
versions.forEach { versions.forEach {
result.task(":$it:build").outcome == SUCCESS result.task(":$it:build").outcome == SUCCESS
} }
@@ -69,4 +69,35 @@ class MultiMcVersionTest extends Specification implements GradleProjectTestTrait
where: where:
version << STANDARD_TEST_VERSIONS 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
}
} }

View File

@@ -22,38 +22,26 @@
* SOFTWARE. * 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.Plugin
import org.gradle.api.internal.catalog.DelegatingProjectDependency import org.gradle.api.Project
import org.gradle.api.internal.project.ProjectInternal import org.gradle.api.plugins.BasePluginExtension
import spock.lang.Specification
import net.fabricmc.loom.util.gradle.GradleUtils class TestPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.group = "com.example"
project.version = "1.0.0"
class GradleUtilsTest extends Specification { project.getExtensions().configure(BasePluginExtension.class) {
def "get default project dependency"() { it.archivesName = project.rootProject.isolated.name + "-" + project.name
given: }
def project = Mock(ProjectInternal)
def dependency = new DefaultProjectDependency(project, false)
when: def minecraftVersion = project.name.substring(7)
def result = GradleUtils.getDependencyProject(dependency)
then: project.getDependencies().add("minecraft", "com.mojang:minecraft:$minecraftVersion")
result == project project.getDependencies().add("mappings", "net.fabricmc:yarn:$minecraftVersion+build.1:v2")
} project.getDependencies().add("modImplementation", "net.fabricmc:fabric-loader:0.16.9")
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
} }
} }

View File

@@ -85,7 +85,7 @@ class TestPlugin implements Plugin<Project> {
def future = handler.daemonConnection.thenAccept { it.waitForAndProcessStop() } def future = handler.daemonConnection.thenAccept { it.waitForAndProcessStop() }
// Stop the daemon // 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 // Wait for the connection to be processed, this should have already happened, as the above call is blocking
future.join() future.join()

View File

@@ -24,7 +24,6 @@
package net.fabricmc.loom.test.unit package net.fabricmc.loom.test.unit
import org.gradle.api.logging.LogLevel
import spock.lang.Specification import spock.lang.Specification
import net.fabricmc.loom.util.ProcessUtil import net.fabricmc.loom.util.ProcessUtil
@@ -32,7 +31,7 @@ import net.fabricmc.loom.util.ProcessUtil
class ProcessUtilTest extends Specification { class ProcessUtilTest extends Specification {
def "print process info"() { def "print process info"() {
when: when:
def output = new ProcessUtil(LogLevel.DEBUG).printWithParents(ProcessHandle.current()) def output = new ProcessUtil(ProcessUtil.ArgumentVisibility.SHOW_SENSITIVE).printWithParents(ProcessHandle.current())
then: then:
// Just a simple check to see if the output is not empty // Just a simple check to see if the output is not empty

View File

@@ -30,7 +30,6 @@ import org.objectweb.asm.tree.ClassNode
import spock.lang.Specification import spock.lang.Specification
import net.fabricmc.loom.util.kotlin.KotlinClasspath import net.fabricmc.loom.util.kotlin.KotlinClasspath
import net.fabricmc.loom.util.kotlin.KotlinPluginUtils
import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader import net.fabricmc.loom.util.kotlin.KotlinRemapperClassloader
import net.fabricmc.tinyremapper.api.TrClass import net.fabricmc.tinyremapper.api.TrClass
import net.fabricmc.tinyremapper.api.TrEnvironment import net.fabricmc.tinyremapper.api.TrEnvironment
@@ -38,9 +37,8 @@ import net.fabricmc.tinyremapper.api.TrRemapper
class KotlinRemapperClassloaderTest extends Specification { class KotlinRemapperClassloaderTest extends Specification {
private static String KOTLIN_VERSION = KotlinVersion.CURRENT.toString() 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_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"() { def "Test Kotlin Remapper Classloader"() {
given: given:

View File

@@ -180,6 +180,10 @@ trait GradleProjectTestTrait {
args << "-Dorg.gradle.unsafe.isolated-projects=true" args << "-Dorg.gradle.unsafe.isolated-projects=true"
} }
if (options.configureOnDemand) {
args << "--configure-on-demand"
}
args.addAll(options.tasks ?: []) args.addAll(options.tasks ?: [])
args << "--stacktrace" args << "--stacktrace"
@@ -278,7 +282,7 @@ trait GradleProjectTestTrait {
return file return file
} }
void buildSrc(String name) { void buildSrc(String name, boolean apply = true) {
useBuildSrc = true useBuildSrc = true
def buildSrcDir = new File(projectDir, "buildSrc") def buildSrcDir = new File(projectDir, "buildSrc")
@@ -305,24 +309,26 @@ trait GradleProjectTestTrait {
rootProject.name='loom-test-plugin' rootProject.name='loom-test-plugin'
''' '''
// Patch the new plugin into the end of the plugins block
def matcher = buildGradle.text =~ /(?s)plugins \{(?<ids>.*?)}/
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 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) def targetSrc = new File(buildSrcDir, "src/main/groovy/net/fabricmc/loom/test/integration/buildSrc/" + name)
FileUtils.copyDirectory(sourceSrc, targetSrc) FileUtils.copyDirectory(sourceSrc, targetSrc)
if (apply) {
// Patch the new plugin into the end of the plugins block
def matcher = buildGradle.text =~ /(?s)plugins \{(?<ids>.*?)}/
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) { void writeBuildSrcDeps(GradleRunner runner) {

View File

@@ -1,6 +1,6 @@
diff --git a/build.gradle b/build.gradle diff --git a/build.gradle b/build.gradle
--- a/build.gradle (revision 41bc64cd617f03d49ecc4a4f7788cb65d465415c) --- a/build.gradle (revision 70277babddfaf52ee30013af94764da19473b3b1)
+++ b/build.gradle (date 1718312645477) +++ b/build.gradle (date 1732875235843)
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
def ENV = System.getenv() 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 version + "+" + latestCommits.get(0).id.substring(0, 8) + DigestUtils.sha256Hex(project.rootProject.minecraft_version).substring(0, 2)
+ return "999.0.0" + return "999.0.0"
} }
def getBranch() { 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 {

View File

@@ -3,8 +3,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
plugins { plugins {
kotlin("jvm") version "1.9.22" kotlin("jvm") version "2.0.21"
kotlin("plugin.serialization") version "1.9.22" kotlin("plugin.serialization") version "2.0.21"
id("dev.architectury.loom") id("dev.architectury.loom")
`maven-publish` `maven-publish`
} }
@@ -31,8 +31,8 @@ version = "0.0.1"
dependencies { dependencies {
minecraft(group = "com.mojang", name = "minecraft", version = "1.16.5") minecraft(group = "com.mojang", name = "minecraft", version = "1.16.5")
mappings(group = "net.fabricmc", name = "yarn", version = "1.16.5+build.5", classifier = "v2") mappings(group = "net.fabricmc", name = "yarn", version = "1.16.5+build.5", classifier = "v2")
modImplementation("net.fabricmc:fabric-loader:0.12.12") modImplementation("net.fabricmc:fabric-loader:0.16.9")
modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.10.17+kotlin.1.9.22") modImplementation(group = "net.fabricmc", name = "fabric-language-kotlin", version = "1.12.3+kotlin.2.0.21")
} }
publishing { publishing {

View File

@@ -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

View File

@@ -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" rootProject.name = "multi-mc-versions"
// Yes lot of mc version // Yes lot of mc version