Skip to content

Commit

Permalink
Transform decompile step into a task that is customizable via specifi…
Browse files Browse the repository at this point in the history
…c settings (#46)

Example:
```gradle
subsystems {
  decompiler {
    maxMemory = "2g"
    maxThreads = 4
    logLevel = DecompilerLogLevel.TRACE
    jvmArgs = ["-verbose:gc']
  }
}
```
  • Loading branch information
shartte authored Dec 1, 2023
1 parent 96b89d4 commit d09a284
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 7 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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>, 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Decompiler> {

/**
* Allows the maximum memory provided to the decompiler to be overridden. Must be specified
* in the "123g" or "123m" form.
*/
@Input
@Optional
@DSLProperty
Property<String> 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<Integer> getMaxThreads();

/**
* The log-level to use for the decompiler. Supported values: trace, info, warn, error.
* Defaults to {@link DecompilerLogLevel#INFO}.
*/
@Input
@Optional
@DSLProperty
Property<DecompilerLogLevel> getLogLevel();

/**
* Allows additional JVM arguments to be added to the decompiler invocation.
*/
@Input
@Optional
@DSLProperty
ListProperty<String> getJvmArgs();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.neoforged.gradle.dsl.common.extensions.subsystems

enum DecompilerLogLevel {
TRACE,
INFO,
WARN,
ERROR
}
Original file line number Diff line number Diff line change
@@ -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<Subsystems> {

/**
* @return settings for the decompiler subsystem
*/
@Nested
@DSLProperty
Decompiler getDecompiler();

}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,14 +12,21 @@
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;
import net.neoforged.gradle.dsl.common.runtime.tasks.tree.TaskTreeAdapter;
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;
Expand All @@ -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;
Expand All @@ -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<NeoFormRuntimeSpecification, NeoFormRuntimeSpecification.Builder, NeoFormRuntimeDefinition> {
public abstract class NeoFormRuntimeExtension extends CommonRuntimeExtension<NeoFormRuntimeSpecification, NeoFormRuntimeSpecification.Builder, NeoFormRuntimeDefinition> implements ConfigurableDSLElement<NeoFormRuntimeExtension> {

@javax.inject.Inject
public NeoFormRuntimeExtension(Project project) {
Expand All @@ -67,6 +82,8 @@ private static void configureMcpRuntimeTaskWithDefaults(NeoFormRuntimeSpecificat
@Nullable
private static TaskProvider<? extends WithOutput> createBuiltIn(final NeoFormRuntimeSpecification spec, NeoFormConfigConfigurationSpecV2 neoFormConfigV2, NeoFormConfigConfigurationSpecV1.Step step, final Map<String, TaskProvider<? extends WithOutput>> tasks, final Map<GameArtifact, TaskProvider<? extends WithOutput>> gameArtifactTaskProviders, final Optional<TaskProvider<? extends WithOutput>> 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");
Expand Down Expand Up @@ -117,7 +134,65 @@ private static TaskProvider<? extends WithOutput> createBuiltIn(final NeoFormRun
return null;
}

private static TaskProvider<? extends Runtime> createExecute(final NeoFormRuntimeSpecification spec, final NeoFormConfigConfigurationSpecV1.Step step, final NeoFormConfigConfigurationSpecV1.Function function) {
@NotNull
private static TaskProvider<? extends Runtime> 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<String> 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<String> 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<? extends Runtime> 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());
Expand Down

0 comments on commit d09a284

Please sign in to comment.