Indentations break the feed.
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / multivm / VMRunTaskDetached.java
blob7ed99d3d4c089da24e62a61ff102c423ebde35e4
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc.squirreljme.plugin.multivm;
12 import cc.squirreljme.plugin.multivm.gdb.GdbUtils;
13 import cc.squirreljme.plugin.multivm.ident.SourceTargetClassifier;
14 import cc.squirreljme.plugin.swm.JavaMEMidlet;
15 import cc.squirreljme.plugin.util.ForwardInputToOutput;
16 import cc.squirreljme.plugin.util.GradleLoggerOutputStream;
17 import cc.squirreljme.plugin.util.JavaExecSpecFiller;
18 import cc.squirreljme.plugin.util.SimpleJavaExecSpecFiller;
19 import java.io.IOException;
20 import java.net.URI;
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.LinkedHashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Random;
29 import org.gradle.api.Project;
30 import org.gradle.api.Task;
31 import org.gradle.api.logging.LogLevel;
32 import org.gradle.api.logging.Logger;
34 /**
35 * Runs the program within the virtual machine.
37 * @since 2024/07/28
39 public class VMRunTaskDetached
41 /** The classifier used. */
42 protected final SourceTargetClassifier classifier;
44 /** The logger for logging messages. */
45 private final Logger _logger;
47 /** The classpath to use. */
48 private final Path[] _classPath;
50 /** The MIDlet to execute, optionally. */
51 private final JavaMEMidlet _midlet;
53 /** Optional main class, required if no MIDlet was specified. */
54 private final String _mainClass;
56 /** The working directory to run under. */
57 private final Path _workDir;
59 /** Context project. */
60 private final Project _anyProject;
62 /** Debug server to use. */
63 private final URI _debugServer;
65 /**
66 * Initializes the detached run task.
68 * @param __classifier The classifier used.
69 * @param __logger The logger to use.
70 * @param __classPath The classpath to use.
71 * @param __midlet The MIDlet to run.
72 * @param __mainClass The main class to run, if there is no MIDlet.
73 * @param __workDir The working directly to run under.
74 * @param __anyProject Any project.
75 * @param __debugServer The debug server to use.
76 * @since 2024/07/28
78 public VMRunTaskDetached(SourceTargetClassifier __classifier,
79 Logger __logger, Path[] __classPath, JavaMEMidlet __midlet,
80 String __mainClass, Path __workDir, Project __anyProject,
81 URI __debugServer)
83 this.classifier = __classifier;
84 this._logger = __logger;
85 this._classPath = __classPath;
86 this._midlet = __midlet;
87 this._mainClass = __mainClass;
88 this._workDir = __workDir;
89 this._anyProject = __anyProject;
90 this._debugServer = __debugServer;
93 /**
94 * {@inheritDoc}
95 * @since 2024/07/28
97 public void run()
99 // Gather the class path to use for target execution, this is all the
100 // SquirrelJME modules this depends on
101 VMSpecifier vmType = this.classifier.getVmType();
102 Path[] classPath = this._classPath;
104 // Debug
105 if (this._logger != null)
106 this._logger.debug("Classpath: {}", Arrays.asList(classPath));
108 // Determine the main entry class or MIDlet to use
109 JavaMEMidlet midlet = this._midlet;
110 String mainClass = VMHelpers.mainClass(midlet, this._mainClass);
112 // Debug
113 if (this._logger != null)
114 this._logger.debug("MIDlet: {}", midlet);
115 if (this._logger != null)
116 this._logger.debug("MainClass: {}", mainClass);
118 // Debugger being used?
119 URI debugServer = this._debugServer;
121 // Standard SquirrelJME command line arguments to use
122 List<String> args = new ArrayList<>();
124 // Process for the debugger, to kill if the main task exits
125 Process debuggerProc = null;
127 // Force interpreter?
128 boolean forceInterpreter = false;
130 // Which command line is used?
131 List<String> procArgs = new ArrayList<>();
132 Map<String, String> sysProps = new LinkedHashMap<>();
133 if (debugServer != null)
135 // LLDB Server?
136 if ("lldb".equals(debugServer.getScheme()))
138 procArgs.add(Paths.get(GdbUtils.setScheme(debugServer,
139 "file")).toAbsolutePath().toString());
140 procArgs.add("gdbserver");
141 procArgs.add("localhost:2345");
142 procArgs.add("--");
144 // Force interpreter to be used
145 forceInterpreter = true;
148 // GDB Server?
149 else if ("gdb".equals(debugServer.getScheme()))
151 procArgs.add(Paths.get(GdbUtils.setScheme(debugServer,
152 "file")).toAbsolutePath().toString());
153 procArgs.add("localhost:2345");
155 // Force interpreter to be used
156 forceInterpreter = true;
159 // WinDbg Server?
160 else if ("windbg".equals(debugServer.getScheme()))
162 procArgs.add(Paths.get(GdbUtils.setScheme(debugServer,
163 "file")).toAbsolutePath().toString());
164 procArgs.add("-server");
165 procArgs.add("tcp:port=2345");
167 // Force interpreter to be used
168 forceInterpreter = true;
171 // JDWP?
172 else if ("jdwp".equals(debugServer.getScheme()))
173 sysProps.put("squirreljme.jdwp",
174 debugServer.getSchemeSpecificPart());
176 // Internal debugger?
177 else if ("internal".equals(debugServer.getScheme()))
179 // Choose random port that is not likely to be used
180 int port = 32767 +
181 new Random(System.currentTimeMillis())
182 .nextInt(32767);
184 // Listen on this port
185 sysProps.put("squirreljme.jdwp",
186 ":" + port);
188 // Use this task as the base and find its output Jar
189 Task debuggerTask = new __FindInternalDebugger__(
190 this._anyProject).call();
191 Path debuggerJar = debuggerTask.getOutputs().getFiles()
192 .getSingleFile().toPath();
194 // Use this Java command
195 List<String> debuggerArgs = new ArrayList<>();
196 Path javaExe = SimpleJavaExecSpecFiller.findJavaExe();
197 debuggerArgs.add((javaExe == null ? "java" :
198 javaExe.toAbsolutePath().toString()));
200 // Use the same classpath as the host
201 debuggerArgs.add("-classpath");
202 debuggerArgs.add(debuggerJar.toAbsolutePath().toString());
204 // Launch into the debugger instead
205 debuggerArgs.add("cc.squirreljme.debugger.Main");
206 debuggerArgs.add("localhost:" + port);
208 // Fork process with the debugger
209 ProcessBuilder builder = new ProcessBuilder(debuggerArgs);
211 // Use our terminal and pipes for the output
212 builder.inheritIO();
214 // Working directory in the build directory
215 builder.directory(this._workDir.toFile());
217 // Start the debugger
220 // Start the debugger
221 debuggerProc = builder.start();
224 // It failed, so cannot debug
225 catch (IOException __e)
227 throw new RuntimeException("Could not launch debugger.",
228 __e);
233 // If executing a MIDlet, then the single main argument is the actual
234 // name of the MIDlet to execute
235 if (midlet != null)
236 args.add(midlet.mainClass);
238 // Debug
239 if (this._logger != null)
240 this._logger.debug("Target Working Dir: {}",
241 System.getProperty("user.dir"));
243 // Execute the virtual machine, if the exit status is non-zero then
244 // the task execution will be considered as a failure
245 JavaExecSpecFiller execSpec = new SimpleJavaExecSpecFiller();
246 vmType.spawnJvmArguments(this._anyProject, this.classifier,
247 true,
248 execSpec, mainClass,
249 (midlet != null ? midlet.mainClass : mainClass),
250 sysProps,
251 classPath, classPath,
252 args.<String>toArray(new String[args.size()]));
254 // Get command line to use
255 List<String> cmdLine = new ArrayList<>();
256 for (String arg : execSpec.getCommandLine())
257 cmdLine.add(arg);
259 // Pull in command line
260 for (int i = 0, n = cmdLine.size(); i < n; i++)
262 // Normally add the command line
263 String arg = cmdLine.get(i);
264 procArgs.add(arg);
266 // If this is the executable, we want to possibly inject
267 // -Xint to prevent compilation from being performed if doing
268 // GDB
269 if (i == 0 && forceInterpreter)
270 procArgs.add("-Xint");
273 // Setup process
274 ProcessBuilder procBuilder = new ProcessBuilder(procArgs);
276 // Working directory in the build directory, for any logs or
277 // otherwise
278 procBuilder.directory(this._anyProject.getBuildDir());
280 // Pipe output
281 procBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);
282 procBuilder.redirectError(ProcessBuilder.Redirect.PIPE);
284 // Run task
285 Process proc = null;
286 ForwardInputToOutput stdOut = null;
287 ForwardInputToOutput stdErr = null;
290 // Start it
291 proc = procBuilder.start();
293 // Forward streams
294 if (this._logger != null)
296 stdOut = new ForwardInputToOutput(
297 proc.getInputStream(), new GradleLoggerOutputStream(
298 this._logger, LogLevel.LIFECYCLE,
299 -1, -1));
300 stdErr = new ForwardInputToOutput(
301 proc.getErrorStream(), new GradleLoggerOutputStream(
302 this._logger, LogLevel.ERROR,
303 -1, -1));
305 else
307 stdOut = new ForwardInputToOutput(proc.getInputStream(),
308 System.out);
309 stdErr = new ForwardInputToOutput(proc.getErrorStream(),
310 System.err);
313 // Run them
314 stdOut.runThread("stdOutPipe");
315 stdErr.runThread("stdErrPipe");
317 // Did the task fail?
318 int exitValue = proc.waitFor();
319 if (exitValue != 0)
320 throw new RuntimeException(
321 String.format("Task exited with: %d %08x",
322 exitValue, exitValue));
325 catch (IOException|InterruptedException __e)
327 // Make sure it really dies
328 if (proc != null)
329 proc.destroyForcibly();
331 // Always kill the debugger process
332 if (debuggerProc != null)
333 debuggerProc.destroyForcibly();
335 // Now fail
336 throw new RuntimeException("Task run failed or was interrupted.",
337 __e);
339 finally
341 // Always kill the debugger process
342 if (debuggerProc != null)
343 debuggerProc.destroyForcibly();
345 if (stdOut != null)
348 stdOut.close();
350 catch (IOException __ignored)
354 if (stdErr != null)
357 stdErr.close();
359 catch (IOException __ignored)