diff --git a/README.md b/README.md index c91efc7e0..b852afb2c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,21 @@ NeoGradle Minecraft mod development framework used by NeoForge and FML for the Gradle build system. -For a quick start, see how the [NeoForge Mod Development Kit](https://github.com/neoforged/MDK) uses NeoGradle, or see our official [Documentation](https://docs.neoforged.net/neogradle/docs/). +For a quick start, see how the [NeoForge Mod Development Kit](https://github.com/neoforged/MDK) uses NeoGradle, or see +our official [Documentation](https://docs.neoforged.net/neogradle/docs/). To see the latest available version of NeoGradle, visit the [NeoForged project page](https://projects.neoforged.net/neoforged/neogradle). + +## Override Decompiler Settings + +The settings used by the decompiler when preparing Minecraft dependencies can be overridden +using [Gradle properties](https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties). +This can be useful to trade slower build-times for being able to run NeoGradle on lower-end machines. + +| Property | Description | +|----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| `neogradle.subsystems.decompiler.maxMemory` | How much heap memory is given to the decompiler. Can be specified either in gigabyte (`4g`) or megabyte (`4096m`). | +| `neogradle.subsystems.decompiler.maxThreads` | By default the decompiler uses all available CPU cores. This setting can be used to limit it to a given number of threads. | +| `neogradle.subsystems.decompiler.logLevel` | Can be used to override the [decompiler loglevel](https://vineflower.org/usage/#cmdoption-log). | +| `neogradle.subsystems.decompiler.jvmArgs` | Pass arbitrary JVM arguments to the decompiler. I.e. `-XX:+HeapDumpOnOutOfMemoryError` | + diff --git a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java index 493ee0db1..ed43589e7 100644 --- a/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java +++ b/common/src/main/java/net/neoforged/gradle/common/CommonProjectPlugin.java @@ -6,6 +6,7 @@ import net.neoforged.gradle.common.extensions.dependency.creation.ProjectBasedDependencyCreator; import net.neoforged.gradle.common.extensions.dependency.replacement.DependencyReplacementsExtension; import net.neoforged.gradle.common.extensions.repository.IvyDummyRepositoryExtension; +import net.neoforged.gradle.common.extensions.subsystems.SubsystemsExtension; import net.neoforged.gradle.common.runs.ide.IdeRunIntegrationManager; import net.neoforged.gradle.common.runs.run.RunImpl; import net.neoforged.gradle.common.runtime.definition.CommonRuntimeDefinition; @@ -20,6 +21,7 @@ import net.neoforged.gradle.dsl.common.extensions.*; import net.neoforged.gradle.dsl.common.extensions.dependency.replacement.DependencyReplacement; import net.neoforged.gradle.dsl.common.extensions.repository.Repository; +import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems; import net.neoforged.gradle.dsl.common.runs.run.Run; import net.neoforged.gradle.dsl.common.runs.type.RunType; import net.neoforged.gradle.dsl.common.util.NamingConstants; @@ -74,6 +76,7 @@ public void apply(Project project) { extensionManager.registerExtension("minecraft", Minecraft.class, (p) -> p.getObjects().newInstance(MinecraftExtension.class, p)); extensionManager.registerExtension("mappings", Mappings.class, (p) -> p.getObjects().newInstance(MappingsExtension.class, p)); + extensionManager.registerExtension("subsystems", Subsystems.class, (p) -> p.getObjects().newInstance(SubsystemsExtension.class, p)); OfficialNamingChannelConfigurator.getInstance().configure(project); diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/SubsystemsExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/SubsystemsExtension.java new file mode 100644 index 000000000..4864fafd8 --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/extensions/subsystems/SubsystemsExtension.java @@ -0,0 +1,39 @@ +package net.neoforged.gradle.common.extensions.subsystems; + +import net.minecraftforge.gdi.ConfigurableDSLElement; +import net.neoforged.gradle.dsl.common.extensions.subsystems.DecompilerLogLevel; +import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.provider.ProviderFactory; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.Locale; + +public abstract class SubsystemsExtension implements ConfigurableDSLElement, Subsystems { + private static final String PROPERTY_PREFIX = "neogradle.subsystems."; + private final Project project; + + @Inject + public SubsystemsExtension(Project project) { + this.project = project; + + ProviderFactory providers = project.getProviders(); + getDecompiler().getMaxMemory().convention(providers.gradleProperty(PROPERTY_PREFIX + "decompiler.maxMemory")); + getDecompiler().getMaxThreads().convention(providers.gradleProperty(PROPERTY_PREFIX + "decompiler.maxThreads").map(Integer::parseUnsignedInt)); + getDecompiler().getLogLevel().convention(providers.gradleProperty(PROPERTY_PREFIX + "decompiler.logLevel").map(s -> { + try { + return DecompilerLogLevel.valueOf(s.toUpperCase(Locale.ROOT)); + } catch (Exception e) { + throw new GradleException("Unknown DecompilerLogLevel: " + s + ". Available options: " + Arrays.toString(DecompilerLogLevel.values())); + } + })); + getDecompiler().getJvmArgs().convention(providers.gradleProperty(PROPERTY_PREFIX + "decompiler.jvmArgs").map(s -> Arrays.asList(s.split("\\s+")))); + } + + @Override + public Project getProject() { + return project; + } +} diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/Decompiler.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/Decompiler.groovy new file mode 100644 index 000000000..7afd7420b --- /dev/null +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/Decompiler.groovy @@ -0,0 +1,52 @@ +package net.neoforged.gradle.dsl.common.extensions.subsystems + +import groovy.transform.CompileStatic +import net.minecraftforge.gdi.ConfigurableDSLElement +import net.minecraftforge.gdi.annotations.DSLProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional + +/** + * Allows configuration of the decompiler used by NeoGradle. + */ +@CompileStatic +interface Decompiler extends ConfigurableDSLElement { + + /** + * Allows the maximum memory provided to the decompiler to be overridden. Must be specified + * in the "123g" or "123m" form. + */ + @Input + @Optional + @DSLProperty + Property getMaxMemory(); + + /** + * Allows the maximum number of threads used by the decompiler to be constrained. By default, it will + * use all available threads. + */ + @Input + @Optional + @DSLProperty + Property getMaxThreads(); + + /** + * The log-level to use for the decompiler. Supported values: trace, info, warn, error. + * Defaults to {@link DecompilerLogLevel#INFO}. + */ + @Input + @Optional + @DSLProperty + Property getLogLevel(); + + /** + * Allows additional JVM arguments to be added to the decompiler invocation. + */ + @Input + @Optional + @DSLProperty + ListProperty getJvmArgs(); + +} diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/DecompilerLogLevel.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/DecompilerLogLevel.groovy new file mode 100644 index 000000000..9e42283fe --- /dev/null +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/DecompilerLogLevel.groovy @@ -0,0 +1,8 @@ +package net.neoforged.gradle.dsl.common.extensions.subsystems + +enum DecompilerLogLevel { + TRACE, + INFO, + WARN, + ERROR +} diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/Subsystems.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/Subsystems.groovy new file mode 100644 index 000000000..ead229a57 --- /dev/null +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/subsystems/Subsystems.groovy @@ -0,0 +1,21 @@ +package net.neoforged.gradle.dsl.common.extensions.subsystems + +import groovy.transform.CompileStatic +import net.minecraftforge.gdi.BaseDSLElement +import net.minecraftforge.gdi.annotations.DSLProperty +import org.gradle.api.tasks.Nested + +/** + * Allows configuration of various NeoGradle subsystems. + */ +@CompileStatic +interface Subsystems extends BaseDSLElement { + + /** + * @return settings for the decompiler subsystem + */ + @Nested + @DSLProperty + Decompiler getDecompiler(); + +} diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/tasks/Execute.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/tasks/Execute.groovy index 01a62a840..59875cb41 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/tasks/Execute.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/tasks/Execute.groovy @@ -129,8 +129,6 @@ interface Execute extends WithWorkspace, WithOutput, WithJavaVersion, ExecuteSpe java.setWorkingDir(me.getOutputDirectory().get()) java.getMainClass().set(mainClass) java.setStandardOutput(log_out) - - java.setMaxHeapSize(String.format("%dm", Runtime.getRuntime().maxMemory().intdiv(1024 * 1024))) }).rethrowFailure().assertNormalExitValue() } } diff --git a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java index ff754647b..f2d01227f 100644 --- a/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java +++ b/neoform/src/main/java/net/neoforged/gradle/neoform/runtime/extensions/NeoFormRuntimeExtension.java @@ -3,6 +3,7 @@ import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import net.minecraftforge.gdi.ConfigurableDSLElement; import net.neoforged.gradle.common.runtime.extensions.CommonRuntimeExtension; import net.neoforged.gradle.common.runtime.tasks.Execute; import net.neoforged.gradle.common.runtime.tasks.ListLibraries; @@ -11,6 +12,9 @@ import net.neoforged.gradle.dsl.common.extensions.Mappings; import net.neoforged.gradle.dsl.common.extensions.Minecraft; import net.neoforged.gradle.dsl.common.extensions.MinecraftArtifactCache; +import net.neoforged.gradle.dsl.common.extensions.subsystems.Decompiler; +import net.neoforged.gradle.dsl.common.extensions.subsystems.DecompilerLogLevel; +import net.neoforged.gradle.dsl.common.extensions.subsystems.Subsystems; import net.neoforged.gradle.dsl.common.runtime.naming.TaskBuildingContext; import net.neoforged.gradle.dsl.common.runtime.tasks.Runtime; import net.neoforged.gradle.dsl.common.runtime.tasks.RuntimeArguments; @@ -18,7 +22,11 @@ import net.neoforged.gradle.dsl.common.tasks.ArtifactProvider; import net.neoforged.gradle.dsl.common.tasks.WithOutput; import net.neoforged.gradle.dsl.common.tasks.specifications.OutputSpecification; -import net.neoforged.gradle.dsl.common.util.*; +import net.neoforged.gradle.dsl.common.util.CommonRuntimeUtils; +import net.neoforged.gradle.dsl.common.util.ConfigurationUtils; +import net.neoforged.gradle.dsl.common.util.DistributionType; +import net.neoforged.gradle.dsl.common.util.GameArtifact; +import net.neoforged.gradle.dsl.common.util.NamingConstants; import net.neoforged.gradle.dsl.neoform.configuration.NeoFormConfigConfigurationSpecV1; import net.neoforged.gradle.dsl.neoform.configuration.NeoFormConfigConfigurationSpecV2; import net.neoforged.gradle.neoform.runtime.definition.NeoFormRuntimeDefinition; @@ -31,6 +39,7 @@ import net.neoforged.gradle.neoform.util.NeoFormRuntimeUtils; import net.neoforged.gradle.util.CopyingFileTreeVisitor; import org.apache.commons.lang3.StringUtils; +import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; @@ -43,11 +52,17 @@ import javax.annotation.Nullable; import java.io.File; import java.io.FileNotFoundException; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; @SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "unused"}) // API Design -public abstract class NeoFormRuntimeExtension extends CommonRuntimeExtension { +public abstract class NeoFormRuntimeExtension extends CommonRuntimeExtension implements ConfigurableDSLElement { @javax.inject.Inject public NeoFormRuntimeExtension(Project project) { @@ -67,6 +82,8 @@ private static void configureMcpRuntimeTaskWithDefaults(NeoFormRuntimeSpecificat @Nullable private static TaskProvider createBuiltIn(final NeoFormRuntimeSpecification spec, NeoFormConfigConfigurationSpecV2 neoFormConfigV2, NeoFormConfigConfigurationSpecV1.Step step, final Map> tasks, final Map> gameArtifactTaskProviders, final Optional> adaptedInput) { switch (step.getType()) { + case "decompile": + return createDecompile(spec, step, neoFormConfigV2); case "downloadManifest": return gameArtifactTaskProviders.computeIfAbsent(GameArtifact.LAUNCHER_MANIFEST, a -> { throw new IllegalStateException("Launcher Manifest is required for this step, but was not provided"); @@ -117,7 +134,65 @@ private static TaskProvider createBuiltIn(final NeoFormRun return null; } - private static TaskProvider createExecute(final NeoFormRuntimeSpecification spec, final NeoFormConfigConfigurationSpecV1.Step step, final NeoFormConfigConfigurationSpecV1.Function function) { + @NotNull + private static TaskProvider createDecompile(NeoFormRuntimeSpecification spec, NeoFormConfigConfigurationSpecV1.Step step, NeoFormConfigConfigurationSpecV2 neoFormConfig) { + NeoFormConfigConfigurationSpecV1.Function function = neoFormConfig.getFunction(step.getType()); + if (function == null) { + throw new IllegalArgumentException(String.format("Invalid NeoForm Config, Unknown function step type: %s File: %s", step.getType(), neoFormConfig)); + } + + // Filter out decompiler arguments that aren't related to its output (log-level and thread-count) + List decompilerArgs = new ArrayList<>(function.getArgs()); + decompilerArgs.removeIf(arg -> arg.startsWith("-log=") || arg.startsWith("-thr=")); + + // Retrieve the default memory size from the JVM arguments configured in NeoForm + String defaultMaxMemory = "4g"; + List jvmArgs = new ArrayList<>(function.getJvmArgs()); + for (int i = jvmArgs.size() - 1; i >= 0; i--) { + if (jvmArgs.get(i).startsWith("-Xmx")) { + defaultMaxMemory = jvmArgs.get(i).substring("-Xmx".length()); + jvmArgs.remove(i); + } + } + + // Consider user-settings + Decompiler settings = spec.getProject().getExtensions().getByType(Subsystems.class).getDecompiler(); + String maxMemory = settings.getMaxMemory().getOrElse(defaultMaxMemory); + int maxThreads = settings.getMaxThreads().getOrElse(0); + String logLevel = getDecompilerLogLevelArg(settings.getLogLevel().getOrElse(DecompilerLogLevel.INFO), function.getVersion()); + + if (settings.getJvmArgs().isPresent()) { + jvmArgs.addAll(settings.getJvmArgs().get()); + } + jvmArgs.add("-Xmx" + maxMemory); + if (maxThreads > 0) { + decompilerArgs.add(0, "-thr=" + maxThreads); + } + decompilerArgs.add(0, "-log=" + logLevel); + + return spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, step.getName()), Execute.class, task -> { + task.getExecutingJar().set(ToolUtilities.resolveTool(task.getProject(), function.getVersion())); + task.getJvmArguments().addAll(jvmArgs); + task.getProgramArguments().addAll(decompilerArgs); + }); + } + + private static String getDecompilerLogLevelArg(DecompilerLogLevel logLevel, String version) { + switch (logLevel) { + case TRACE: + return "trace"; + case INFO: + return "info"; + case WARN: + return "warn"; + case ERROR: + return "error"; + default: + throw new GradleException("LogLevel " + logLevel + " not supported by " + version); + } + } + + private TaskProvider createExecute(final NeoFormRuntimeSpecification spec, final NeoFormConfigConfigurationSpecV1.Step step, final NeoFormConfigConfigurationSpecV1.Function function) { return spec.getProject().getTasks().register(CommonRuntimeUtils.buildTaskName(spec, step.getName()), Execute.class, task -> { task.getExecutingJar().set(ToolUtilities.resolveTool(task.getProject(), function.getVersion())); task.getJvmArguments().addAll(function.getJvmArgs());