1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
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
;
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
;
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
;
35 * Runs the program within the virtual machine.
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
;
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.
78 public VMRunTaskDetached(SourceTargetClassifier __classifier
,
79 Logger __logger
, Path
[] __classPath
, JavaMEMidlet __midlet
,
80 String __mainClass
, Path __workDir
, Project __anyProject
,
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
;
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
;
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
);
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)
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");
144 // Force interpreter to be used
145 forceInterpreter
= true;
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;
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;
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
181 new Random(System
.currentTimeMillis())
184 // Listen on this port
185 sysProps
.put("squirreljme.jdwp",
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
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.",
233 // If executing a MIDlet, then the single main argument is the actual
234 // name of the MIDlet to execute
236 args
.add(midlet
.mainClass
);
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
,
249 (midlet
!= null ? midlet
.mainClass
: mainClass
),
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())
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
);
266 // If this is the executable, we want to possibly inject
267 // -Xint to prevent compilation from being performed if doing
269 if (i
== 0 && forceInterpreter
)
270 procArgs
.add("-Xint");
274 ProcessBuilder procBuilder
= new ProcessBuilder(procArgs
);
276 // Working directory in the build directory, for any logs or
278 procBuilder
.directory(this._anyProject
.getBuildDir());
281 procBuilder
.redirectOutput(ProcessBuilder
.Redirect
.PIPE
);
282 procBuilder
.redirectError(ProcessBuilder
.Redirect
.PIPE
);
286 ForwardInputToOutput stdOut
= null;
287 ForwardInputToOutput stdErr
= null;
291 proc
= procBuilder
.start();
294 if (this._logger
!= null)
296 stdOut
= new ForwardInputToOutput(
297 proc
.getInputStream(), new GradleLoggerOutputStream(
298 this._logger
, LogLevel
.LIFECYCLE
,
300 stdErr
= new ForwardInputToOutput(
301 proc
.getErrorStream(), new GradleLoggerOutputStream(
302 this._logger
, LogLevel
.ERROR
,
307 stdOut
= new ForwardInputToOutput(proc
.getInputStream(),
309 stdErr
= new ForwardInputToOutput(proc
.getErrorStream(),
314 stdOut
.runThread("stdOutPipe");
315 stdErr
.runThread("stdErrPipe");
317 // Did the task fail?
318 int exitValue
= proc
.waitFor();
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
329 proc
.destroyForcibly();
331 // Always kill the debugger process
332 if (debuggerProc
!= null)
333 debuggerProc
.destroyForcibly();
336 throw new RuntimeException("Task run failed or was interrupted.",
341 // Always kill the debugger process
342 if (debuggerProc
!= null)
343 debuggerProc
.destroyForcibly();
350 catch (IOException __ignored
)
359 catch (IOException __ignored
)