Skip to content

Commit f8fc9ef

Browse files
committed
Migrate ToolExecBase from JavaExec to DefaultTask
Gradle please get your shit together. When configuration cache is enabled, log levels passed into `Task#getLogging`'s standard output captures are discarded. I'm just going to try using `ExecOperations#javaexec` manually to get what I want.
1 parent 582afe1 commit f8fc9ef

File tree

3 files changed

+155
-51
lines changed

3 files changed

+155
-51
lines changed

gradleutils-shared/src/main/java/net/minecraftforge/gradleutils/shared/EnhancedProblems.java

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -223,34 +223,6 @@ final RuntimeException pluginNotYetApplied(Exception e) {
223223
}
224224
//endregion
225225

226-
//region ToolExecBase
227-
final void reportToolExecNotEnhanced(Task task) {
228-
String className = task.getClass().getSimpleName().replace('.', '-');
229-
task.getLogger().warn("WARNING: {} doesn't implement EnhancedTask", className);
230-
this.report(String.format("%s-not-enhanced", className.toLowerCase(Locale.ROOT)), String.format("%s doesn't implement EnhancedTask", className), spec -> spec
231-
.details("""
232-
Implementing subclasses of ToolExecBase should also implement (a subclass of) EnhancedTask.
233-
Not doing so will result in global caches being ignored. Please check your implementations.
234-
Affected task: %s (%s)""".formatted(task, task.getClass()))
235-
.severity(Severity.WARNING)
236-
.stackLocation()
237-
.solution("Double check your task implementation."));
238-
}
239-
240-
final void reportToolExecEagerArgs(Task task) {
241-
String className = task.getClass().getSimpleName().replace('.', '-');
242-
task.getLogger().warn("WARNING: {} implementation adds arguments without using addArguments()", className);
243-
this.report(String.format("%s-eager-args", className.toLowerCase(Locale.ROOT)), String.format("%s implementation adds arguments without using addArguments()", className), spec -> spec
244-
.details("""
245-
A ToolExecBase task is eagerly adding arguments using JavaExec#args without using ToolExecBase#addArguments.
246-
This may cause unintended behavior as the arguemnts given to the tool will contain the arguments in both of these method calls.
247-
Affected task: %s (%s)""".formatted(task, task.getClass()))
248-
.severity(Severity.WARNING)
249-
.stackLocation()
250-
.solution("Use ToolExecBase#addArguments"));
251-
}
252-
//endregion
253-
254226
//region Utilities
255227

256228
/// A utility method to ensure that a [FileSystemLocation] [Provider] has (its parent) directory created. If the

gradleutils-shared/src/main/java/net/minecraftforge/gradleutils/shared/SharedUtil.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import org.gradle.api.artifacts.Dependency;
1616
import org.gradle.api.artifacts.FileCollectionDependency;
1717
import org.gradle.api.artifacts.ModuleVersionSelector;
18+
import org.gradle.api.logging.LogLevel;
19+
import org.gradle.api.logging.Logger;
1820
import org.gradle.api.plugins.ExtensionContainer;
1921
import org.gradle.api.plugins.JavaPluginExtension;
2022
import org.gradle.api.provider.Property;
@@ -222,6 +224,14 @@ public List<String> getArgs() {
222224

223225
//region Action Logging
224226

227+
/// Creates an output stream that logs to the given action.
228+
///
229+
/// @param logger The logger to log to
230+
/// @return The output stream
231+
public static PipedOutputStream toLog(Logger logger, LogLevel level) {
232+
return toLog(s -> logger.log(level, s));
233+
}
234+
225235
/// Creates an output stream that logs to the given action.
226236
///
227237
/// @param logger The logger to log to

gradleutils-shared/src/main/java/net/minecraftforge/gradleutils/shared/ToolExecBase.java

Lines changed: 145 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,45 @@
44
*/
55
package net.minecraftforge.gradleutils.shared;
66

7+
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
8+
import org.gradle.api.DefaultTask;
79
import org.gradle.api.Transformer;
10+
import org.gradle.api.file.ConfigurableFileCollection;
811
import org.gradle.api.file.DirectoryProperty;
912
import org.gradle.api.file.FileSystemLocation;
1013
import org.gradle.api.file.FileSystemLocationProperty;
1114
import org.gradle.api.logging.LogLevel;
15+
import org.gradle.api.logging.LoggingManager;
16+
import org.gradle.api.model.ObjectFactory;
1217
import org.gradle.api.provider.ListProperty;
18+
import org.gradle.api.provider.MapProperty;
19+
import org.gradle.api.provider.Property;
1320
import org.gradle.api.provider.Provider;
21+
import org.gradle.api.provider.ProviderFactory;
22+
import org.gradle.api.tasks.Classpath;
23+
import org.gradle.api.tasks.Console;
1424
import org.gradle.api.tasks.Input;
25+
import org.gradle.api.tasks.InputFiles;
1526
import org.gradle.api.tasks.Internal;
1627
import org.gradle.api.tasks.JavaExec;
28+
import org.gradle.api.tasks.Nested;
1729
import org.gradle.api.tasks.Optional;
30+
import org.gradle.api.tasks.TaskAction;
31+
import org.gradle.jvm.toolchain.JavaLauncher;
32+
import org.gradle.process.ExecOperations;
33+
import org.gradle.process.ExecResult;
1834
import org.jetbrains.annotations.MustBeInvokedByOverriders;
35+
import org.jetbrains.annotations.Nullable;
1936
import org.jetbrains.annotations.UnknownNullability;
2037

2138
import javax.inject.Inject;
2239
import java.io.File;
40+
import java.util.ArrayList;
41+
import java.util.HashMap;
42+
import java.util.List;
2343
import java.util.Locale;
2444
import java.util.Map;
45+
import java.util.Objects;
2546
import java.util.concurrent.Callable;
2647

2748
/// This tool execution task is a template on top of [JavaExec] to make executing [tools][Tool] much easier and more
@@ -32,12 +53,12 @@
3253
/// should extend from.
3354
/// @see JavaExec
3455
/// @see Tool
35-
public abstract class ToolExecBase<P extends EnhancedProblems> extends JavaExec implements EnhancedTask<P> {
36-
private final P problems;
56+
public abstract class ToolExecBase<P extends EnhancedProblems> extends DefaultTask implements EnhancedTask<P> {
57+
private final P problems = this.getObjects().newInstance(this.problemsType());
3758

3859
/// The default tool directory (usage is not required).
39-
protected final DirectoryProperty defaultToolDir = this.getObjectFactory().directoryProperty();
40-
private final ListProperty<String> additionalArgs = this.getObjectFactory().listProperty(String.class);
60+
protected final DirectoryProperty defaultToolDir = this.getObjects().directoryProperty();
61+
private final ListProperty<String> additionalArgs = this.getObjects().listProperty(String.class);
4162

4263
/// Additional arguments to use when invoking the tool. Use in configuration instead of [#args].
4364
///
@@ -46,6 +67,31 @@ public abstract class ToolExecBase<P extends EnhancedProblems> extends JavaExec
4667
return this.additionalArgs;
4768
}
4869

70+
//region JavaExec
71+
protected abstract @InputFiles @Classpath ConfigurableFileCollection getClasspath();
72+
73+
protected abstract @Input @Optional Property<String> getMainClass();
74+
75+
protected abstract @Nested Property<JavaLauncher> getJavaLauncher();
76+
//endregion
77+
78+
//region Logging
79+
@Deprecated
80+
@Override public LoggingManager getLogging() {
81+
return super.getLogging();
82+
}
83+
84+
protected abstract @Console Property<LogLevel> getStandardOutputLogLevel();
85+
86+
protected abstract @Console Property<LogLevel> getStandardErrorLogLevel();
87+
//endregion
88+
89+
protected abstract @Inject ObjectFactory getObjects();
90+
91+
protected abstract @Inject ProviderFactory getProviders();
92+
93+
protected abstract @Inject ExecOperations getExecOperations();
94+
4995
/// Creates a new task instance using the given types and tool information.
5096
///
5197
/// @param tool The tool to use for this task
@@ -55,22 +101,19 @@ public abstract class ToolExecBase<P extends EnhancedProblems> extends JavaExec
55101
/// best practice is to make a single `ToolExec` class for the implementing plugin to use, which other tasks can
56102
/// extend off of.
57103
protected ToolExecBase(Tool tool) {
58-
this.problems = this.getObjectFactory().newInstance(this.problemsType());
59-
60104
var resolved = this.getTool(tool);
61-
this.defaultToolDir.value(
105+
SharedUtil.finalizeProperty(this.defaultToolDir.value(
62106
this.globalCaches().dir(tool.getName().toLowerCase(Locale.ENGLISH)).map(this.ensureFileLocationInternal())
63-
);
64-
65-
this.setClasspath(resolved.getClasspath());
107+
));
66108

67-
SharedUtil.finalizeProperty(this.defaultToolDir);
109+
this.getClasspath().setFrom(resolved.getClasspath());
68110

69111
if (resolved.hasMainClass())
70112
this.getMainClass().set(resolved.getMainClass());
71113
this.getJavaLauncher().set(resolved.getJavaLauncher());
72114

73-
this.getLogging().captureStandardOutput(LogLevel.LIFECYCLE).captureStandardError(LogLevel.ERROR);
115+
this.getStandardOutputLogLevel().convention(LogLevel.LIFECYCLE);
116+
this.getStandardErrorLogLevel().convention(LogLevel.ERROR);
74117
}
75118

76119
/// The enhanced problems instance to use for this task.
@@ -87,22 +130,65 @@ private <T extends FileSystemLocation> Transformer<T, T> ensureFileLocationInter
87130
/// This method should be overridden by subclasses to add arguments to this task via [JavaExec#args]. To preserve
88131
/// arguments added by superclasses, this method [must be invoked by overriders][MustBeInvokedByOverriders].
89132
@MustBeInvokedByOverriders
90-
protected void addArguments() {
91-
this.args(this.getAdditionalArgs().get());
92-
}
133+
protected void addArguments() { }
134+
135+
private transient boolean executing = false;
136+
private transient @Nullable List<Provider<String>> args;
137+
private transient @Nullable List<Provider<String>> jvmArgs;
138+
private transient @Nullable Map<String, String> environment;
139+
private transient @Nullable Map<String, String> systemProperties;
93140

94141
/// @implNote Not invoking this method from an overriding method *will* result in the tool never being executed and
95142
/// [#addArguments()] never being run.
96-
@Override
97-
public void exec() {
98-
if (!this.getArgs().isEmpty())
99-
this.getProblems().reportToolExecEagerArgs(this);
143+
@TaskAction
144+
protected ExecResult exec() {
145+
this.executing = true;
146+
this.args = new ArrayList<>();
147+
this.jvmArgs = new ArrayList<>();
148+
this.environment = new HashMap<>();
149+
this.systemProperties = new HashMap<>();
100150

101151
this.addArguments();
152+
this.args(this.getAdditionalArgs().get());
102153

103-
this.getLogger().info("{} {}", this.getClasspath().getAsPath(), String.join(" ", this.getArgs()));
154+
var args = DefaultGroovyMethods.collect(this.args, Closures.<Provider<String>, String>function(Provider::get));
155+
var jvmArgs = DefaultGroovyMethods.collect(this.jvmArgs, Closures.<Provider<String>, String>function(Provider::get));
156+
this.getLogger().info("{} {}", this.getClasspath().getAsPath(), String.join(" ", args));
104157

105-
super.exec();
158+
var stdOutLevel = this.getStandardOutputLogLevel().get();
159+
var stdErrLevel = this.getStandardErrorLogLevel().get();
160+
return this.getExecOperations().javaexec(spec -> {
161+
spec.setClasspath(this.getClasspath());
162+
spec.getMainClass().set(this.getMainClass());
163+
spec.setExecutable(this.getJavaLauncher().get().getExecutablePath().getAsFile().getAbsolutePath());
164+
spec.setArgs(args);
165+
spec.setJvmArgs(jvmArgs);
166+
spec.setEnvironment(this.environment);
167+
spec.setSystemProperties(this.systemProperties);
168+
169+
spec.setStandardOutput(SharedUtil.toLog(this.getLogger(), stdOutLevel));
170+
spec.setErrorOutput(SharedUtil.toLog(this.getLogger(), stdErrLevel));
171+
});
172+
}
173+
174+
protected final void args(Object... args) {
175+
try {
176+
for (var arg : args) {
177+
Objects.requireNonNull(this.args).add(this.getProviders().provider(arg::toString));
178+
}
179+
} catch (NullPointerException e) {
180+
throw new IllegalStateException("ToolExecBase#jvmArgs can only be called inside of #addArguments()", e);
181+
}
182+
}
183+
184+
protected final void args(Iterable<?> args) {
185+
try {
186+
for (var arg : args) {
187+
Objects.requireNonNull(this.args).add(this.getProviders().provider(arg::toString));
188+
}
189+
} catch (NullPointerException e) {
190+
throw new IllegalStateException("ToolExecBase#jvmArgs can only be called inside of #addArguments()", e);
191+
}
106192
}
107193

108194
/// Adds each file to the arguments preceded by the given argument. Designed to work well with
@@ -152,9 +238,45 @@ protected final void args(Map<?, ?> args) {
152238
var key = entry.getKey();
153239
var value = entry.getValue();
154240
this.args(
155-
key instanceof Provider<?> provider ? provider.map(Object::toString).get() : this.getProviderFactory().provider(key::toString).get(),
156-
value instanceof Provider<?> provider ? (provider instanceof FileSystemLocationProperty<?> file ? file.getLocationOnly() : provider) : this.getProviderFactory().provider(() -> value)
241+
key instanceof Provider<?> provider ? provider.map(Object::toString).get() : this.getProviders().provider(key::toString).get(),
242+
value instanceof Provider<?> provider ? (provider instanceof FileSystemLocationProperty<?> file ? file.getLocationOnly() : provider) : this.getProviders().provider(() -> value)
157243
);
158244
}
159245
}
246+
247+
protected final void jvmArgs(Object... args) {
248+
try {
249+
for (var arg : args) {
250+
Objects.requireNonNull(this.jvmArgs).add(this.getProviders().provider(arg::toString));
251+
}
252+
} catch (NullPointerException e) {
253+
throw new IllegalStateException("ToolExecBase#jvmArgs can only be called inside of #addArguments()", e);
254+
}
255+
}
256+
257+
protected final void jvmArgs(Iterable<?> args) {
258+
try {
259+
for (var arg : args) {
260+
Objects.requireNonNull(this.jvmArgs).add(this.getProviders().provider(arg::toString));
261+
}
262+
} catch (NullPointerException e) {
263+
throw new IllegalStateException("ToolExecBase#jvmArgs can only be called inside of #addArguments()", e);
264+
}
265+
}
266+
267+
protected final void environment(String key, String value) {
268+
try {
269+
Objects.requireNonNull(this.environment).put(key, value);
270+
} catch (NullPointerException e) {
271+
throw new IllegalStateException("ToolExecBase#environment can only be called inside of #addArguments()", e);
272+
}
273+
}
274+
275+
protected final void systemProperty(String key, String value) {
276+
try {
277+
Objects.requireNonNull(this.systemProperties).put(key, value);
278+
} catch (NullPointerException e) {
279+
throw new IllegalStateException("ToolExecBase#systemProperty can only be called inside of #addArguments()", e);
280+
}
281+
}
160282
}

0 commit comments

Comments
 (0)