From 268e595ec1297dbf9a66deeae48e29266190880b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 22 Nov 2023 18:18:41 +0100 Subject: [PATCH] Add Chrome devtools and DAP tools for debugging (#8344) Adds chrome-inspector tool and Debug Adapter protocol tool for debugging Enso. # Important Notes The chrome devtools seems to be broken (tracked in https://github.com/oracle/graal/issues/7636). Even `graalpy --inspect ...` fails (Chrome devtools freezes after connecting to the server). This PR adds Debug adapter protocol tool for debugging as a workaround for chrome inspector. There is docs in [dap.md](https://github.com/enso-org/enso/pull/8344/files#diff-421574b50574cfe546e86d4b3d32d79b8b2087f2fe204f68e5cf2693af43bbe1) --- build.sbt | 4 ++- docs/debugger/README.md | 2 ++ docs/debugger/dap.md | 31 ++++++++++++++++ .../org/enso/runner/ContextFactory.scala | 35 +++++++++++++------ .../interpreter/test/DebuggingEnsoTest.java | 21 +++++++---- project/GraalVM.scala | 6 +++- project/JPMSUtils.scala | 2 +- 7 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 docs/debugger/dap.md diff --git a/build.sbt b/build.sbt index 19faa32766..e219b193e0 100644 --- a/build.sbt +++ b/build.sbt @@ -1428,7 +1428,9 @@ lazy val runtime = (project in file("engine/runtime")) GraalVM.modules.map(_.withConfigurations(Some(Runtime.name))) val langs = GraalVM.langsPkgs.map(_.withConfigurations(Some(Runtime.name))) - necessaryModules ++ langs + val tools = + GraalVM.toolsPkgs.map(_.withConfigurations(Some(Runtime.name))) + necessaryModules ++ langs ++ tools }, Test / javaOptions ++= testLogProviderOptions ++ Seq( "-Dpolyglotimpl.DisableClassPathIsolation=true" diff --git a/docs/debugger/README.md b/docs/debugger/README.md index b45d073bc4..8536eb1b95 100644 --- a/docs/debugger/README.md +++ b/docs/debugger/README.md @@ -15,6 +15,8 @@ used by Enso, broken up as follows: Debugger. - [**Chrome devtools debugger:**](./chrome-devtools.md) A guide how to debug Enso code using Chrome devtools. +- [**Debug adapter protocol:**](./dap.md) A guide how to debug Enso code using + VSCode. - [**Debugging Enso and Java code at once:**](./mixed-debugging.md) A step-by-step guide how to debug both Enso and Java code in a single debugger. - [**Debugging Engine (Runtime) only Java code:**](./runtime-debugging.md) A diff --git a/docs/debugger/dap.md b/docs/debugger/dap.md new file mode 100644 index 0000000000..475c52e95f --- /dev/null +++ b/docs/debugger/dap.md @@ -0,0 +1,31 @@ +# Debug Adapter Protocol + +[Debug Adapter Protocol](https://www.graalvm.org/latest/tools/dap/) is yet +another instrument available for a Truffle language. The DAP is a native +protocol for VSCode and as such, works only via VSCode. To start Enso with DAP +server waiting for a client to attach, launch enso via: + +``` +env JAVA_OPTS='-Dpolyglot.dap' ./built-distribution/enso-engine-*/enso-*/bin/enso --run *.enso +``` + +Once DAP server is started and ready for a client to be attached, the following +output will be printed: + +``` +[Graal DAP] Starting server and listening on /127.0.0.1:4711 +``` + +There is a +[Launch configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) +in the repository in +[.vscode/launch.json](https://github.com/enso-org/enso/blob/a123cd0d9f4b04d05aae7a5231efba554062188f/.vscode/launch.json#L13-L16) +with name `Debug Adapter Protocol`, you can start debugging via +[Run and Debug view](https://code.visualstudio.com/docs/editor/debugging#_run-and-debug-view) +by selecting the `Debug adapter protocol` configuration and pressing play: +![image](https://github.com/enso-org/enso/assets/14013887/7f15abfd-b4fa-45d3-a100-142c465b6444) + +Another screenshot showing the DAP in action: +![image](https://github.com/enso-org/enso/assets/14013887/41dd8b80-dbac-4a11-b3e2-97c99e42c507) + +Note that the port 4711 is the default port for DAP. diff --git a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala index 9d2b02da77..4f7baedd7b 100644 --- a/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala +++ b/engine/runner/src/main/scala/org/enso/runner/ContextFactory.scala @@ -11,11 +11,11 @@ import org.enso.polyglot.{ PolyglotContext, RuntimeOptions } -import org.graalvm.polyglot.Engine -import org.graalvm.polyglot.Context +import org.graalvm.polyglot.{Context, Engine} import org.slf4j.event.Level -import java.io.{File, InputStream, OutputStream} +import java.io.{ByteArrayOutputStream, File, InputStream, OutputStream} +import scala.util.{Failure, Success, Using} /** Utility class for creating Graal polyglot contexts. */ @@ -131,14 +131,7 @@ class ContextFactory { if (graalpy.exists()) { builder.option("python.Executable", graalpy.getAbsolutePath()); } - if ( - Engine - .newBuilder() - .allowExperimentalOptions(true) - .build() - .getLanguages() - .containsKey("java") - ) { + if (engineHasJava()) { builder .option("java.ExposeNativeJavaVM", "true") .option("java.Polyglot", "true") @@ -148,4 +141,24 @@ class ContextFactory { } new PolyglotContext(builder.build) } + + /** Checks whether the polyglot engine has Espresso. + * + * Creates a temporary polyglot engine for that and makes sure that it is closed. + */ + private def engineHasJava(): Boolean = { + Using( + Engine + .newBuilder() + .allowExperimentalOptions(true) + .out(new ByteArrayOutputStream()) + .err(new ByteArrayOutputStream()) + .build() + ) { engine => + engine.getLanguages.containsKey("java") + } match { + case Success(ret) => ret + case Failure(ex) => throw new IllegalStateException("unreachable", ex) + } + } } diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java index 1d10d37dc4..4cc1aa981b 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java @@ -457,17 +457,20 @@ public class DebuggingEnsoTest { .collect(Collectors.toList()); } - @Ignore @Test public void testSteppingOver() { Source src = createEnsoSource(""" - baz x = x # 1 - bar x = baz x # 2 - foo x = # 3 - bar 42 # 4 - end = 0 # 5 + baz x = x # 1 + bar x = # 2 + ret = baz x # 3 + ret # 4 + foo x = # 5 + bar 42 # 6 + end = 0 # 7 """); - List expectedLineNumbers = List.of(3, 4, 5); + // Steps into line 2 - declaration of the method, which is fine. + // (5, 6, 7) would be better. + List expectedLineNumbers = List.of(5, 6, 2, 7); Queue steps = createStepOverEvents(expectedLineNumbers.size()); testStepping(src, "foo", new Object[]{0}, steps, expectedLineNumbers); } @@ -475,6 +478,10 @@ public class DebuggingEnsoTest { /** * Use some methods from Vector in stdlib. Stepping over methods from different * modules might be problematic. + * + * TODO[pm] This test is ignored, because the current behavior of step over is that it first + * steps into the declaration (name) of the method that is being stepped over and then + * steps back. So there would be weird line numbers from std lib. */ @Ignore @Test diff --git a/project/GraalVM.scala b/project/GraalVM.scala index 0e156acbb7..8e5335341c 100644 --- a/project/GraalVM.scala +++ b/project/GraalVM.scala @@ -79,7 +79,11 @@ object GraalVM { "org.graalvm.tools" % "profiler-tool" % version ) - val toolsPkgs = chromeInspectorPkgs + val debugAdapterProtocolPkgs = Seq( + "org.graalvm.tools" % "dap-tool" % version + ) + + val toolsPkgs = chromeInspectorPkgs ++ debugAdapterProtocolPkgs // TODO: Add graalvmPython val langsPkgs = jsPkgs diff --git a/project/JPMSUtils.scala b/project/JPMSUtils.scala index 6a68aa4181..694c42897d 100644 --- a/project/JPMSUtils.scala +++ b/project/JPMSUtils.scala @@ -33,7 +33,7 @@ object JPMSUtils { * When invoking the `java` command, these modules need to be put on the module-path. */ val componentModules: Seq[ModuleID] = - GraalVM.modules ++ GraalVM.langsPkgs ++ Seq( + GraalVM.modules ++ GraalVM.langsPkgs ++ GraalVM.toolsPkgs ++ Seq( "org.slf4j" % "slf4j-api" % slf4jVersion, "ch.qos.logback" % "logback-classic" % logbackClassicVersion, "ch.qos.logback" % "logback-core" % logbackClassicVersion