-
-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathPrepareRunOrTest.java
More file actions
346 lines (293 loc) · 13 KB
/
Copy pathPrepareRunOrTest.java
File metadata and controls
346 lines (293 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
package net.neoforged.moddevgradle.internal;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;
import net.neoforged.moddevgradle.internal.utils.FileUtils;
import net.neoforged.moddevgradle.internal.utils.OperatingSystem;
import net.neoforged.moddevgradle.internal.utils.StringUtils;
import net.neoforged.moddevgradle.internal.utils.VersionCapabilitiesInternal;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
import org.jetbrains.annotations.Nullable;
import org.slf4j.event.Level;
/**
* Performs preparation for running the game or running a test.
*
* <p><ul>
* <li>Writes the JVM and program arguments for running the game to args-files.</li>
* <li>Creates the run folder.</li>
* </ul>
*/
abstract class PrepareRunOrTest extends DefaultTask {
@Internal
public abstract DirectoryProperty getGameDirectory();
@OutputFile
public abstract RegularFileProperty getVmArgsFile();
@OutputFile
public abstract RegularFileProperty getProgramArgsFile();
@OutputFile
@Optional
public abstract RegularFileProperty getLog4jConfigFile();
/**
* The source of the underlying run type templates. This must contain a single file, which has one of the
* following supported formats:
* <ul>
* <li>NeoForge Userdev config.json file</li>
* <li>NeoForge Userdev jar file, containing a Userdev config.json file</li>
* </ul>
* Subclasses implementing {@link #resolveRunType} can then access this information to get the run type template.
*/
@Classpath
public abstract ConfigurableFileCollection getRunTypeTemplatesSource();
@InputFile
@PathSensitive(PathSensitivity.NONE)
public abstract RegularFileProperty getAssetProperties();
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getLegacyClasspathFile();
@Classpath
@InputFiles
abstract ConfigurableFileCollection getModules();
@Classpath
@InputFiles
abstract ConfigurableFileCollection getJavaAgent();
@Input
public abstract MapProperty<String, String> getSystemProperties();
@Input
public abstract ListProperty<String> getJvmArguments();
@Input
public abstract ListProperty<String> getProgramArguments();
@Input
public abstract Property<Level> getGameLogLevel();
/**
* Only used when {@link #getRunTypeTemplatesSource()} is empty,
* to know whether the associated Minecraft version has separate entrypoints for generating resource- and
* data packs.
* Defaults to latest.
*/
@Input
@Optional
public abstract Property<VersionCapabilitiesInternal> getVersionCapabilities();
/**
* The property that decides whether DevLogin is enabled.
*/
@Input
public abstract Property<Boolean> getDevLogin();
private final ProgramArgsFormat programArgsFormat;
protected PrepareRunOrTest(ProgramArgsFormat programArgsFormat) {
this.programArgsFormat = programArgsFormat;
getVersionCapabilities().convention(VersionCapabilitiesInternal.latest());
getDevLogin().convention(false);
}
protected abstract UserDevRunType resolveRunType(UserDevConfig userDevConfig);
@Nullable
protected abstract String resolveMainClass(UserDevRunType runConfig);
@Internal
protected abstract boolean isClientDistribution();
private List<String> getInterpolatedJvmArgs(UserDevRunType runConfig) {
var result = new ArrayList<String>();
for (var jvmArg : runConfig.jvmArgs()) {
String arg = jvmArg;
if (arg.equals("{modules}")) {
arg = getModules().getFiles().stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining(File.pathSeparator));
}
result.add(RunUtils.escapeJvmArg(arg));
}
if (isClientDistribution() && OperatingSystem.current() == OperatingSystem.MACOS) {
// TODO: it might be more future-proof to source this from the platform args in the MC version json
result.add("-XstartOnFirstThread");
}
return result;
}
@TaskAction
public void prepareRun() throws IOException {
// Make sure the run directory exists
// IntelliJ refuses to start a run configuration whose working directory does not exist
var runDir = getGameDirectory().get().getAsFile();
Files.createDirectories(runDir.toPath());
// If no NeoForge userdev config is set, we only support Vanilla run types
UserDevRunType runConfig;
if (getRunTypeTemplatesSource().isEmpty()) {
runConfig = resolveRunType(getSimulatedUserDevConfigForVanilla());
} else {
var userDevConfig = loadUserDevConfig(getRunTypeTemplatesSource().getSingleFile());
runConfig = resolveRunType(userDevConfig);
}
var mainClass = resolveMainClass(runConfig);
var devLogin = getDevLogin().get();
var sysProps = new LinkedHashMap<String, String>();
// When DevLogin is used, we swap out the main class with the DevLogin one, and add the actual main class as a system property
if (devLogin && mainClass != null) {
sysProps.put("devlogin.launch_target", mainClass);
mainClass = RunUtils.DEV_LOGIN_MAIN_CLASS;
}
writeJvmArguments(runConfig, sysProps);
writeProgramArguments(runConfig, mainClass);
}
private UserDevConfig loadUserDevConfig(File userDevFile) {
// For backwards compatibility reasons we also support loading this from the userdev jar,
// for NeoForge and Forge versions that didn't publish the configuration as a separate JSON to Maven
if (userDevFile.getName().endsWith(".jar")) {
try (var zf = new ZipFile(userDevFile)) {
var configJson = zf.getEntry("config.json");
if (configJson != null) {
try (var in = zf.getInputStream(configJson)) {
return UserDevConfig.from(in);
}
}
} catch (IOException e) {
throw new GradleException("Failed to read userdev config file from Jar-file " + userDevFile, e);
}
}
try (var in = Files.newInputStream(userDevFile.toPath())) {
return UserDevConfig.from(in);
} catch (Exception e) {
throw new GradleException("Failed to read userdev config file from " + userDevFile, e);
}
}
private UserDevConfig getSimulatedUserDevConfigForVanilla() {
var clientArgs = List.of("--gameDir", ".", "--assetIndex", "{asset_index}", "--assetsDir", "{assets_root}", "--accessToken", "NotValid", "--version", "ModDevGradle");
var commonArgs = List.<String>of();
var runTypes = new LinkedHashMap<String, UserDevRunType>();
runTypes.put("client", new UserDevRunType(
true, "net.minecraft.client.main.Main", clientArgs, List.of(), Map.of(), Map.of()));
runTypes.put("server", new UserDevRunType(
true, "net.minecraft.server.Main", commonArgs, List.of(), Map.of(), Map.of()));
if (getVersionCapabilities().getOrElse(VersionCapabilitiesInternal.latest()).splitDataRuns()) {
runTypes.put("clientData", new UserDevRunType(
true, "net.minecraft.client.data.Main", commonArgs, List.of(), Map.of(), Map.of()));
runTypes.put("serverData", new UserDevRunType(
true, "net.minecraft.data.Main", commonArgs, List.of(), Map.of(), Map.of()));
} else {
runTypes.put("data", new UserDevRunType(
true, "net.minecraft.data.Main", commonArgs, List.of(), Map.of(), Map.of()));
}
return new UserDevConfig(runTypes);
}
private void writeJvmArguments(UserDevRunType runConfig, Map<String, String> additionalProperties) throws IOException {
var lines = new ArrayList<String>();
for (var file : getJavaAgent()) {
lines.add("-javaagent:" + file.getAbsolutePath());
}
lines.addAll(getInterpolatedJvmArgs(runConfig));
var userJvmArgs = getJvmArguments().get();
if (!userJvmArgs.isEmpty()) {
lines.add("");
lines.add("# User JVM Arguments");
for (var userJvmArg : userJvmArgs) {
lines.add(RunUtils.escapeJvmArg(userJvmArg));
}
lines.add("");
}
if (getLog4jConfigFile().isPresent()) {
var log4jConfigFile = getLog4jConfigFile().get().getAsFile();
RunUtils.writeLog4j2Configuration(getGameLogLevel().get(), log4jConfigFile.toPath());
lines.add(RunUtils.escapeJvmArg("-Dlog4j2.configurationFile=" + log4jConfigFile.getAbsolutePath()));
}
for (var prop : runConfig.props().entrySet()) {
var propValue = prop.getValue();
if (propValue.equals("{minecraft_classpath_file}")) {
propValue = getLegacyClasspathFile().getAsFile().get().getAbsolutePath();
}
addSystemProp(prop.getKey(), propValue, lines);
}
additionalProperties.putAll(getSystemProperties().get());
for (var entry : additionalProperties.entrySet()) {
addSystemProp(entry.getKey(), entry.getValue(), lines);
}
FileUtils.writeLinesSafe(
getVmArgsFile().get().getAsFile().toPath(),
lines,
// JVM expects default character set
StringUtils.getNativeCharset());
}
private void writeProgramArguments(UserDevRunType runConfig, @Nullable String mainClass) throws IOException {
var lines = new ArrayList<String>();
if (mainClass != null) {
lines.add("# Main Class");
lines.add(mainClass);
lines.add("");
}
lines.add("# NeoForge Run-Type Program Arguments");
var assetProperties = RunUtils.loadAssetProperties(getAssetProperties().get().getAsFile());
List<String> args = runConfig.args();
for (String arg : args) {
switch (arg) {
case "{assets_root}" -> arg = Objects.requireNonNull(assetProperties.assetsRoot(), "assets_root");
case "{asset_index}" -> arg = Objects.requireNonNull(assetProperties.assetIndex(), "asset_index");
}
// FML JUnit simply expects one line per argument
if (programArgsFormat == ProgramArgsFormat.FML_JUNIT) {
lines.add(arg);
} else {
lines.add(RunUtils.escapeJvmArg(arg));
}
}
lines.add("");
lines.add("# User Supplied Program Arguments");
for (var arg : getProgramArguments().get()) {
// FML JUnit simply expects one line per argument
if (programArgsFormat == ProgramArgsFormat.FML_JUNIT) {
lines.add(arg);
} else {
lines.add(RunUtils.escapeJvmArg(arg));
}
}
// For FML JUnit, we need to drop comments + empty lines
if (programArgsFormat == ProgramArgsFormat.FML_JUNIT) {
lines.removeIf(line -> {
line = line.strip();
return line.isEmpty() || line.startsWith("#");
});
}
FileUtils.writeLinesSafe(
getProgramArgsFile().get().getAsFile().toPath(),
lines,
// FML Junit and DevLaunch (starting in 1.0.1) read this file using UTF-8
StandardCharsets.UTF_8);
}
private static void addSystemProp(String name, String value, List<String> lines) {
lines.add(RunUtils.escapeJvmArg("-D" + name + "=" + value));
}
/**
* Declares the format of the program arguments file being written.
*/
protected enum ProgramArgsFormat {
/**
* Format as used by JVM @-files
*/
JVM_ARGFILE,
/**
* Format as used by FML JUnit, which expects
* a file to be passed in the system property "fml.junit.argsfile", containing one program argument per line
* without consideration for escaping.
*/
FML_JUNIT
}
}