Skip to content

Commit a870cd0

Browse files
committed
GradleJob Refactor
1 parent 2269fd3 commit a870cd0

File tree

6 files changed

+171
-144
lines changed

6 files changed

+171
-144
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package processing.app.gradle
2+
3+
import com.sun.jdi.Bootstrap
4+
import com.sun.jdi.VirtualMachine
5+
import com.sun.jdi.connect.AttachingConnector
6+
import kotlinx.coroutines.delay
7+
import processing.app.Messages
8+
import kotlin.time.Duration.Companion.seconds
9+
import kotlin.time.TimeSource
10+
11+
class Debugger {
12+
companion object {
13+
suspend fun connect(port: Int?): VirtualMachine? {
14+
try {
15+
Messages.log("Attaching to VM $port")
16+
val connector = Bootstrap.virtualMachineManager().allConnectors()
17+
.firstOrNull { it.name() == "com.sun.jdi.SocketAttach" }
18+
as AttachingConnector?
19+
?: throw IllegalStateException("No socket attach connector found")
20+
val args = connector.defaultArguments()
21+
args["port"]?.setValue(port?.toString() ?: "5005")
22+
23+
// Try to attach the debugger, retrying if it fails
24+
val start = TimeSource.Monotonic.markNow()
25+
while (start.elapsedNow() < 10.seconds) {
26+
try {
27+
val sketch = connector.attach(args)
28+
sketch.resume()
29+
Messages.log("Attached to VM: ${sketch.name()}")
30+
return sketch
31+
} catch (e: Exception) {
32+
Messages.log("Error while attaching to VM: ${e.message}... Retrying")
33+
}
34+
delay(250)
35+
}
36+
} catch (e: Exception) {
37+
Messages.log("Error while attaching to VM: ${e.message}")
38+
return null
39+
}
40+
return null
41+
}
42+
}
43+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package processing.app.gradle
2+
3+
import com.sun.jdi.ObjectReference
4+
import com.sun.jdi.StackFrame
5+
import com.sun.jdi.StringReference
6+
import com.sun.jdi.VirtualMachine
7+
import com.sun.jdi.event.ExceptionEvent
8+
import com.sun.jdi.request.EventRequest
9+
import kotlinx.coroutines.CoroutineScope
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.delay
12+
import kotlinx.coroutines.launch
13+
import processing.app.Messages
14+
15+
class Exceptions {
16+
companion object {
17+
suspend fun listen(vm: VirtualMachine) {
18+
try {
19+
val manager = vm.eventRequestManager()
20+
21+
val request = manager.createExceptionRequest(null, false, true)
22+
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD)
23+
request.enable()
24+
25+
val queue = vm.eventQueue()
26+
while (true) {
27+
val eventSet = queue.remove()
28+
for (event in eventSet) {
29+
if (event is ExceptionEvent) {
30+
printExceptionDetails(event)
31+
event.thread().resume()
32+
}
33+
}
34+
eventSet.resume()
35+
delay(10)
36+
}
37+
} catch (e: Exception) {
38+
Messages.log("Error while listening for exceptions: ${e.message}")
39+
}
40+
}
41+
42+
fun printExceptionDetails(event: ExceptionEvent) {
43+
val exception = event.exception()
44+
val thread = event.thread()
45+
val location = event.location()
46+
val stackFrames = thread.frames()
47+
48+
println("\n🚨 Exception Caught 🚨")
49+
println("Type : ${exception.referenceType().name()}")
50+
// println("Message : ${getExceptionMessage(exception)}")
51+
println("Thread : ${thread.name()}")
52+
println("Location : ${location.sourcePath()}:${location.lineNumber()}\n")
53+
54+
// Separate stack frames
55+
val userFrames = mutableListOf<StackFrame>()
56+
val processingFrames = mutableListOf<StackFrame>()
57+
58+
stackFrames.forEach { frame ->
59+
val className = frame.location().declaringType().name()
60+
if (className.startsWith("processing.")) {
61+
processingFrames.add(frame)
62+
} else {
63+
userFrames.add(frame)
64+
}
65+
}
66+
67+
// Print user frames first
68+
println("🔍 Stacktrace (Your Code First):")
69+
userFrames.forEachIndexed { index, frame -> printStackFrame(index, frame) }
70+
71+
// Print Processing frames second
72+
if (processingFrames.isNotEmpty()) {
73+
println("\n🔧 Processing Stacktrace (Hidden Initially):")
74+
processingFrames.forEachIndexed { index, frame -> printStackFrame(index, frame) }
75+
}
76+
77+
println("──────────────────────────────────\n")
78+
}
79+
80+
fun printStackFrame(index: Int, frame: StackFrame) {
81+
val location = frame.location()
82+
val method = location.method()
83+
println(
84+
" #$index ${location.sourcePath()}:${location.lineNumber()} -> ${
85+
method.declaringType().name()
86+
}.${method.name()}()"
87+
)
88+
}
89+
90+
// Extracts the exception's message
91+
fun getExceptionMessage(exception: ObjectReference): String {
92+
val messageMethod = exception.referenceType().methodsByName("getMessage").firstOrNull() ?: return "Unknown"
93+
val messageValue =
94+
exception.invokeMethod(null, messageMethod, emptyList(), ObjectReference.INVOKE_SINGLE_THREADED)
95+
return (messageValue as? StringReference)?.value() ?: "Unknown"
96+
}
97+
}
98+
}

app/src/processing/app/gradle/GradleJob.kt

Lines changed: 18 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@ package processing.app.gradle
33
import androidx.compose.runtime.mutableStateListOf
44
import androidx.compose.runtime.mutableStateOf
55
import com.sun.jdi.*
6-
import com.sun.jdi.connect.AttachingConnector
7-
import com.sun.jdi.event.ExceptionEvent
8-
import com.sun.jdi.request.EventRequest
96
import kotlinx.coroutines.CoroutineScope
107
import kotlinx.coroutines.Dispatchers
11-
import kotlinx.coroutines.delay
128
import kotlinx.coroutines.launch
139
import org.gradle.tooling.BuildLauncher
1410
import org.gradle.tooling.GradleConnector
@@ -24,11 +20,7 @@ import processing.app.Messages
2420
import java.io.InputStreamReader
2521
import java.io.PipedInputStream
2622
import java.io.PipedOutputStream
27-
import kotlin.time.Duration.Companion.seconds
28-
import kotlin.time.TimeSource
2923

30-
31-
// TODO: Refactor, reduce its scope
3224
// TODO: Move the error reporting to its own file
3325
// TODO: Move the output filtering to its own file
3426
abstract class GradleJob{
@@ -65,17 +57,10 @@ abstract class GradleJob{
6557
withCancellationToken(cancel.token())
6658
addStateListener()
6759
addDebugging()
68-
if(Base.DEBUG) {
69-
setStandardOutput(System.out)
70-
setStandardError(System.err)
71-
} else {
72-
setStandardOutput(outputStream)
73-
setStandardError(errorStream)
74-
}
60+
setStandardOutput(outputStream)
61+
setStandardError(errorStream)
7562
run()
7663
}
77-
78-
7964
}catch (e: Exception){
8065
Messages.log("Error while running: ${e.message}")
8166
}finally {
@@ -130,7 +115,6 @@ abstract class GradleJob{
130115
fun cancel(){
131116
cancel.cancel()
132117
}
133-
134118
private fun BuildLauncher.addStateListener(){
135119
addProgressListener(ProgressListener { event ->
136120
if(event is TaskStartEvent) {
@@ -154,137 +138,36 @@ abstract class GradleJob{
154138
}
155139
}
156140
if(event is DefaultSingleProblemEvent) {
141+
// TODO: Move to UI instead of printing
157142
if(event.definition.severity == Severity.ADVICE) return@ProgressListener
158143
problems.add(event)
159144

160145
val path = (event.locations.firstOrNull() as DefaultFileLocation?)?.path
161-
// Not Trimming indent as the content might contain newlines
162-
service?.editor?.console?.err?.println(
163-
"""
164-
${event.definition.id.displayName}:
165-
${event.contextualLabel.contextualLabel}
166-
167-
${event.details.details?.replace(path ?: "", "")}
168-
${event.solutions.joinToString("\n") { it.solution }}
169-
"""
170-
)
171-
}
172-
})
173-
}
174-
175-
private fun listenForExceptions(virtualMachine: VirtualMachine){
176-
scope.launch {
177-
try {
178-
val manager = virtualMachine.eventRequestManager()
179-
180-
val request = manager.createExceptionRequest(null, false, true)
181-
request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD)
182-
request.enable()
183-
184-
val queue = virtualMachine.eventQueue()
185-
while (true) {
186-
val eventSet = queue.remove()
187-
for(event in eventSet) {
188-
if (event is ExceptionEvent) {
189-
printExceptionDetails(event)
190-
event.thread().resume()
191-
}
192-
}
193-
eventSet.resume()
194-
}
195-
} catch (e: Exception) {
196-
Messages.log("Error while listening for exceptions: ${e.message}")
197-
}
198-
199-
}
200-
}
201-
fun printExceptionDetails(event: ExceptionEvent) {
202-
val exception = event.exception()
203-
val thread = event.thread()
204-
val location = event.location()
205-
val stackFrames = thread.frames()
206-
207-
println("\n🚨 Exception Caught 🚨")
208-
println("Type : ${exception.referenceType().name()}")
209-
// println("Message : ${getExceptionMessage(exception)}")
210-
println("Thread : ${thread.name()}")
211-
println("Location : ${location.sourcePath()}:${location.lineNumber()}\n")
212146

213-
// Separate stack frames
214-
val userFrames = mutableListOf<StackFrame>()
215-
val processingFrames = mutableListOf<StackFrame>()
147+
val header = """
148+
${event.definition.id.displayName}:
149+
${event.contextualLabel.contextualLabel}
150+
""".trimIndent()
216151

217-
stackFrames.forEach { frame ->
218-
val className = frame.location().declaringType().name()
219-
if (className.startsWith("processing.")) {
220-
processingFrames.add(frame)
221-
} else {
222-
userFrames.add(frame)
152+
val details = event.details.details?.replace(path ?: "", "")
153+
val solutions = event.solutions.joinToString("\n") { it.solution }
154+
val content = "$header\n$details\n$solutions"
155+
service?.editor?.console?.err?.println(content)
223156
}
224-
}
225-
226-
// Print user frames first
227-
println("🔍 Stacktrace (Your Code First):")
228-
userFrames.forEachIndexed { index, frame -> printStackFrame(index, frame) }
229-
230-
// Print Processing frames second
231-
if (processingFrames.isNotEmpty()) {
232-
println("\n🔧 Processing Stacktrace (Hidden Initially):")
233-
processingFrames.forEachIndexed { index, frame -> printStackFrame(index, frame) }
234-
}
235-
236-
println("──────────────────────────────────\n")
237-
}
238-
239-
fun printStackFrame(index: Int, frame: StackFrame) {
240-
val location = frame.location()
241-
val method = location.method()
242-
println(" #$index ${location.sourcePath()}:${location.lineNumber()} -> ${method.declaringType().name()}.${method.name()}()")
243-
}
244-
245-
// Extracts the exception's message
246-
fun getExceptionMessage(exception: ObjectReference): String {
247-
val messageMethod = exception.referenceType().methodsByName("getMessage").firstOrNull() ?: return "Unknown"
248-
val messageValue = exception.invokeMethod(null, messageMethod, emptyList(), ObjectReference.INVOKE_SINGLE_THREADED)
249-
return (messageValue as? StringReference)?.value() ?: "Unknown"
157+
})
250158
}
251159

252-
253-
private fun BuildLauncher.addDebugging(){
160+
fun BuildLauncher.addDebugging() {
254161
addProgressListener(ProgressListener { event ->
255162
if (event !is TaskStartEvent) return@ProgressListener
256163
if (event.descriptor.name != ":run") return@ProgressListener
257164

258-
try {
259-
val port = service?.debugPort.toString()
260-
Messages.log("Attaching to VM $port")
261-
val connector = Bootstrap.virtualMachineManager().allConnectors()
262-
.firstOrNull { it.name() == "com.sun.jdi.SocketAttach" }
263-
as AttachingConnector?
264-
?: throw IllegalStateException("No socket attach connector found")
265-
val args = connector.defaultArguments()
266-
args["port"]?.setValue(port)
267-
268-
// Try to attach the debugger, retrying if it fails
269-
scope.launch {
270-
val start = TimeSource.Monotonic.markNow()
271-
while (start.elapsedNow() < 10.seconds) {
272-
try {
273-
val sketch = connector.attach(args)
274-
vm.value = sketch
275-
sketch.resume()
276-
Messages.log("Attached to VM: ${sketch.name()}")
277-
listenForExceptions(sketch)
278-
break
279-
} catch (e: Exception) {
280-
Messages.log("Error while attaching to VM: ${e.message}... Retrying")
281-
}
282-
delay(250)
283-
}
284-
}
285-
} catch (e: Exception) {
286-
Messages.log("Error while attaching to VM: ${e.message}")
165+
scope.launch {
166+
val debugger = Debugger.connect(service?.debugPort) ?: return@launch
167+
vm.value = debugger
168+
Exceptions.listen(debugger)
287169
}
170+
288171
})
289172
}
290173
}

app/src/processing/app/gradle/GradleService.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import kotlin.io.path.writeText
2626
// TODO: PoC new debugger/tweak mode
2727
// TODO: Allow for plugins to skip gradle entirely
2828
// TODO: Improve background building
29+
// TODO: Rename to Service?
30+
// TODO: Track build speed (for analytics?)
2931

3032
// The gradle service runs the gradle tasks and manages the gradle connection
3133
// It will create the necessary build files for gradle to run
@@ -63,7 +65,7 @@ class GradleService(val editor: Editor) {
6365
}
6466

6567

66-
fun startBuilding(){
68+
private fun startBuilding(){
6769
scope.launch {
6870
// TODO: Improve the experience with unsaved
6971
val job = BackgroundGradleJob()

app/src/processing/app/gradle/ScreenshotService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import kotlin.time.TimeSource
1313

1414
// TODO: Move to java mode
1515
// TODO: Add more feedback when things go wrong
16+
// TODO: Check if the sketch has a draw method
1617
class ScreenshotService {
1718
companion object{
1819
fun takeScreenshot(vm: VirtualMachine, onComplete: (Path) -> Unit) {

0 commit comments

Comments
 (0)