[Lldb-commits] [lldb] [lldb-dap] Implement command directives (PR #74808)

Walter Erquinigo via lldb-commits lldb-commits at lists.llvm.org
Tue Dec 12 12:33:32 PST 2023


https://github.com/walter-erquinigo updated https://github.com/llvm/llvm-project/pull/74808

>From e4c6bed8327aaedab1b5bd0a13e7408dc4d152e4 Mon Sep 17 00:00:00 2001
From: walter erquinigo <walter at modular.com>
Date: Fri, 8 Dec 2023 00:11:19 -0500
Subject: [PATCH] [lldb-dap] Implement quiet commands

This adds support for optionally prefixing any command with `?`, which effectively prevents the output of these commands to be printed to the console unless they fail. This comes handy when programmaticaly running commands on behalf of the user without wanting them to know unless they fail.

In a later PR I plan to implement the `!` prefix for commands that abort lldb-dap upon errors.
---
 .../test/tools/lldb-dap/lldbdap_testcase.py   |  7 ++
 .../test/API/tools/lldb-dap/commands/Makefile |  3 +
 .../lldb-dap/commands/TestDAP_commands.py     | 75 +++++++++++++++++++
 .../test/API/tools/lldb-dap/commands/main.cpp |  1 +
 lldb/tools/lldb-dap/DAP.cpp                   | 49 ++++++++++--
 lldb/tools/lldb-dap/DAP.h                     | 17 +++--
 lldb/tools/lldb-dap/LLDBUtils.cpp             | 69 ++++++++++++++---
 lldb/tools/lldb-dap/LLDBUtils.h               | 37 ++++++++-
 lldb/tools/lldb-dap/lldb-dap.cpp              | 45 ++++++++---
 lldb/tools/lldb-dap/package.json              | 24 +++---
 10 files changed, 278 insertions(+), 49 deletions(-)
 create mode 100644 lldb/test/API/tools/lldb-dap/commands/Makefile
 create mode 100644 lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py
 create mode 100644 lldb/test/API/tools/lldb-dap/commands/main.cpp

diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index 4ccd6014e54be..7436b9900e98b 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -291,6 +291,7 @@ def attach(
         postRunCommands=None,
         sourceMap=None,
         sourceInitFile=False,
+        expectFailure=False,
     ):
         """Build the default Makefile target, create the DAP debug adaptor,
         and attach to the process.
@@ -322,6 +323,8 @@ def cleanup():
             postRunCommands=postRunCommands,
             sourceMap=sourceMap,
         )
+        if expectFailure:
+            return response
         if not (response and response["success"]):
             self.assertTrue(
                 response["success"], "attach failed (%s)" % (response["message"])
@@ -437,6 +440,8 @@ def build_and_launch(
         commandEscapePrefix=None,
         customFrameFormat=None,
         customThreadFormat=None,
+        launchCommands=None,
+        expectFailure=False,
     ):
         """Build the default Makefile target, create the DAP debug adaptor,
         and launch the process.
@@ -470,4 +475,6 @@ def build_and_launch(
             commandEscapePrefix=commandEscapePrefix,
             customFrameFormat=customFrameFormat,
             customThreadFormat=customThreadFormat,
+            launchCommands=launchCommands,
+            expectFailure=expectFailure,
         )
diff --git a/lldb/test/API/tools/lldb-dap/commands/Makefile b/lldb/test/API/tools/lldb-dap/commands/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/commands/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py b/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py
new file mode 100644
index 0000000000000..16794982c10fb
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py
@@ -0,0 +1,75 @@
+import os
+
+import dap_server
+import lldbdap_testcase
+from lldbsuite.test import lldbtest, lldbutil
+from lldbsuite.test.decorators import *
+
+
+class TestDAP_commands(lldbdap_testcase.DAPTestCaseBase):
+    def test_command_directive_quiet_on_success(self):
+        program = self.getBuildArtifact("a.out")
+        command_quiet = (
+            "settings set target.show-hex-variable-values-with-leading-zeroes false"
+        )
+        command_not_quiet = (
+            "settings set target.show-hex-variable-values-with-leading-zeroes true"
+        )
+        self.build_and_launch(
+            program,
+            initCommands=["?" + command_quiet, command_not_quiet],
+            terminateCommands=["?" + command_quiet, command_not_quiet],
+            stopCommands=["?" + command_quiet, command_not_quiet],
+            exitCommands=["?" + command_quiet, command_not_quiet],
+        )
+        full_output = self.collect_console(duration=1.0)
+        self.assertNotIn(command_quiet, full_output)
+        self.assertIn(command_not_quiet, full_output)
+
+    def do_test_abort_on_error(
+        self,
+        use_init_commands=False,
+        use_launch_commands=False,
+        use_pre_run_commands=False,
+    ):
+        program = self.getBuildArtifact("a.out")
+        command_quiet = (
+            "settings set target.show-hex-variable-values-with-leading-zeroes false"
+        )
+        command_abort_on_error = "settings set foo bar"
+        commands = ["?!" + command_quiet, "!" + command_abort_on_error]
+        self.build_and_launch(
+            program,
+            initCommands=commands if use_init_commands else None,
+            launchCommands=commands if use_launch_commands else None,
+            preRunCommands=commands if use_pre_run_commands else None,
+            expectFailure=True,
+        )
+        full_output = self.collect_console(duration=1.0)
+        self.assertNotIn(command_quiet, full_output)
+        self.assertIn(command_abort_on_error, full_output)
+
+    def test_command_directive_abort_on_error_init_commands(self):
+        self.do_test_abort_on_error(use_init_commands=True)
+
+    def test_command_directive_abort_on_error_launch_commands(self):
+        self.do_test_abort_on_error(use_launch_commands=True)
+
+    def test_command_directive_abort_on_error_pre_run_commands(self):
+        self.do_test_abort_on_error(use_pre_run_commands=True)
+
+    def test_command_directive_abort_on_error_attach_commands(self):
+        program = self.getBuildArtifact("a.out")
+        command_quiet = (
+            "settings set target.show-hex-variable-values-with-leading-zeroes false"
+        )
+        command_abort_on_error = "settings set foo bar"
+        self.build_and_create_debug_adaptor()
+        self.attach(
+            program,
+            attachCommands=["?!" + command_quiet, "!" + command_abort_on_error],
+            expectFailure=True,
+        )
+        full_output = self.collect_console(duration=1.0)
+        self.assertNotIn(command_quiet, full_output)
+        self.assertIn(command_abort_on_error, full_output)
diff --git a/lldb/test/API/tools/lldb-dap/commands/main.cpp b/lldb/test/API/tools/lldb-dap/commands/main.cpp
new file mode 100644
index 0000000000000..76e8197013aab
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/commands/main.cpp
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 4926620973976..21bf2d325957c 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -434,20 +434,53 @@ ExpressionContext DAP::DetectExpressionContext(lldb::SBFrame &frame,
   return ExpressionContext::Variable;
 }
 
-void DAP::RunLLDBCommands(llvm::StringRef prefix,
-                          const std::vector<std::string> &commands) {
-  SendOutput(OutputType::Console,
-             llvm::StringRef(::RunLLDBCommands(prefix, commands)));
+bool DAP::RunLLDBCommands(llvm::StringRef prefix,
+                          llvm::ArrayRef<std::string> commands) {
+  bool fatal_error;
+  std::string output = ::RunLLDBCommands(prefix, commands, fatal_error);
+  SendOutput(OutputType::Console, output);
+  return !fatal_error;
+}
+
+static llvm::Error createRunLLDBCommandsErrorMessage(llvm::StringRef category) {
+  return llvm::createStringError(
+      llvm::inconvertibleErrorCode(),
+      llvm::formatv(
+          "Failed to run {0} commands. See the Debug Console for more details.",
+          category)
+          .str()
+          .c_str());
+}
+
+llvm::Error
+DAP::RunAttachCommands(llvm::ArrayRef<std::string> attach_commands) {
+  if (!RunLLDBCommands("Running attachCommands:", attach_commands))
+    return createRunLLDBCommandsErrorMessage("attach");
+  return llvm::Error::success();
 }
 
-void DAP::RunInitCommands() {
-  RunLLDBCommands("Running initCommands:", init_commands);
+llvm::Error
+DAP::RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands) {
+  if (!RunLLDBCommands("Running launchCommands:", launch_commands))
+    return createRunLLDBCommandsErrorMessage("launch");
+  return llvm::Error::success();
 }
 
-void DAP::RunPreRunCommands() {
-  RunLLDBCommands("Running preRunCommands:", pre_run_commands);
+llvm::Error DAP::RunInitCommands() {
+  if (!RunLLDBCommands("Running initCommands:", init_commands))
+    return createRunLLDBCommandsErrorMessage("initCommands");
+  return llvm::Error::success();
 }
 
+llvm::Error DAP::RunPreRunCommands() {
+  if (!RunLLDBCommands("Running preRunCommands:", pre_run_commands))
+    return createRunLLDBCommandsErrorMessage("preRunCommands");
+  return llvm::Error::success();
+}
+
+void DAP::RunPostRunCommands() {
+  RunLLDBCommands("Running postRunCommands:", post_run_commands);
+}
 void DAP::RunStopCommands() {
   RunLLDBCommands("Running stopCommands:", stop_commands);
 }
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index c7d56a06bfa1f..20817194de2d8 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -158,6 +158,7 @@ struct DAP {
   std::vector<ExceptionBreakpoint> exception_breakpoints;
   std::vector<std::string> init_commands;
   std::vector<std::string> pre_run_commands;
+  std::vector<std::string> post_run_commands;
   std::vector<std::string> exit_commands;
   std::vector<std::string> stop_commands;
   std::vector<std::string> terminate_commands;
@@ -227,11 +228,17 @@ struct DAP {
   ExpressionContext DetectExpressionContext(lldb::SBFrame &frame,
                                             std::string &text);
 
-  void RunLLDBCommands(llvm::StringRef prefix,
-                       const std::vector<std::string> &commands);
-
-  void RunInitCommands();
-  void RunPreRunCommands();
+  /// \return
+  ///   \b false if a fatal error was found while executing these commands,
+  ///   according to the rules of \a LLDBUtils::RunLLDBCommands.
+  bool RunLLDBCommands(llvm::StringRef prefix,
+                       llvm::ArrayRef<std::string> commands);
+
+  llvm::Error RunAttachCommands(llvm::ArrayRef<std::string> attach_commands);
+  llvm::Error RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands);
+  llvm::Error RunInitCommands();
+  llvm::Error RunPreRunCommands();
+  void RunPostRunCommands();
   void RunStopCommands();
   void RunExitCommands();
   void RunTerminateCommands();
diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp
index 955c11f636895..c726f1237cad7 100644
--- a/lldb/tools/lldb-dap/LLDBUtils.cpp
+++ b/lldb/tools/lldb-dap/LLDBUtils.cpp
@@ -11,18 +11,24 @@
 
 namespace lldb_dap {
 
-void RunLLDBCommands(llvm::StringRef prefix,
+bool RunLLDBCommands(llvm::StringRef prefix,
                      const llvm::ArrayRef<std::string> &commands,
-                     llvm::raw_ostream &strm) {
+                     llvm::raw_ostream &strm, bool parse_command_directives) {
   if (commands.empty())
-    return;
-  lldb::SBCommandInterpreter interp = g_dap.debugger.GetCommandInterpreter();
-  if (!prefix.empty())
-    strm << prefix << "\n";
-  for (const auto &command : commands) {
-    lldb::SBCommandReturnObject result;
+    return true;
+
+  bool did_print_prefix = false;
+
+  auto print_result = [&](llvm::StringRef command,
+                          lldb::SBCommandReturnObject &result,
+                          llvm::raw_ostream &strm) {
+    // We only print the prefix if we are effectively printing at least one
+    // command.
+    if (!did_print_prefix && !prefix.empty()) {
+      strm << prefix << "\n";
+      did_print_prefix = true;
+    }
     strm << "(lldb) " << command << "\n";
-    interp.HandleCommand(command.c_str(), result);
     auto output_len = result.GetOutputSize();
     if (output_len) {
       const char *output = result.GetOutput();
@@ -33,18 +39,59 @@ void RunLLDBCommands(llvm::StringRef prefix,
       const char *error = result.GetError();
       strm << error;
     }
+  };
+
+  lldb::SBCommandInterpreter interp = g_dap.debugger.GetCommandInterpreter();
+  for (llvm::StringRef command : commands) {
+    lldb::SBCommandReturnObject result;
+    bool quiet_on_success = false;
+    bool check_error = false;
+
+    while (parse_command_directives) {
+      if (command.starts_with("?")) {
+        command = command.drop_front();
+        quiet_on_success = true;
+      } else if (command.starts_with("!")) {
+        command = command.drop_front();
+        check_error = true;
+      } else {
+        break;
+      }
+    }
+
+    interp.HandleCommand(command.str().c_str(), result);
+    const bool got_error = !result.Succeeded();
+    // The if statement below is assuming we always print out `!` prefixed
+    // lines. The only time we don't print is when we have `quiet_on_success ==
+    // true` and we don't have an error.
+    if (quiet_on_success ? got_error : true)
+      print_result(command, result, strm);
+    if (check_error && got_error)
+      return false; // Stop running commands.
   }
+  return true;
 }
 
 std::string RunLLDBCommands(llvm::StringRef prefix,
-                            const llvm::ArrayRef<std::string> &commands) {
+                            const llvm::ArrayRef<std::string> &commands,
+                            bool &fatal_error, bool parse_command_directives) {
+  fatal_error = false;
   std::string s;
   llvm::raw_string_ostream strm(s);
-  RunLLDBCommands(prefix, commands, strm);
+  fatal_error =
+      !RunLLDBCommands(prefix, commands, strm, parse_command_directives);
   strm.flush();
   return s;
 }
 
+std::string
+RunLLDBCommandsVerbatim(llvm::StringRef prefix,
+                        const llvm::ArrayRef<std::string> &commands) {
+  bool fatal_error;
+  return RunLLDBCommands(prefix, commands, fatal_error,
+                         /*parse_command_directives=*/false);
+}
+
 bool ThreadHasStopReason(lldb::SBThread &thread) {
   switch (thread.GetStopReason()) {
   case lldb::eStopReasonTrace:
diff --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h
index a99f798835370..586410c35b48c 100644
--- a/lldb/tools/lldb-dap/LLDBUtils.h
+++ b/lldb/tools/lldb-dap/LLDBUtils.h
@@ -23,6 +23,12 @@ namespace lldb_dap {
 /// All output from every command, including the prompt + the command
 /// is placed into the "strm" argument.
 ///
+/// Each individual command can be prefixed with \b ! and/or \b ? in no
+/// particular order. If \b ? is provided, then the output of that command is
+/// only emitted if it fails, and if \b ! is provided, then the output is
+/// emitted regardless, and \b false is returned without executing the
+/// remaining commands.
+///
 /// \param[in] prefix
 ///     A string that will be printed into \a strm prior to emitting
 ///     the prompt + command and command output. Can be NULL.
@@ -33,9 +39,17 @@ namespace lldb_dap {
 /// \param[in] strm
 ///     The stream that will receive the prefix, prompt + command and
 ///     all command output.
-void RunLLDBCommands(llvm::StringRef prefix,
+///
+/// \param[in] parse_command_directives
+///     If \b false, then command prefixes like \b ! or \b ? are not parsed and
+///     each command is executed verbatim.
+///
+/// \return
+///     \b true, unless a command prefixed with \b ! fails and parsing of
+///     command directives is enabled.
+bool RunLLDBCommands(llvm::StringRef prefix,
                      const llvm::ArrayRef<std::string> &commands,
-                     llvm::raw_ostream &strm);
+                     llvm::raw_ostream &strm, bool parse_command_directives);
 
 /// Run a list of LLDB commands in the LLDB command interpreter.
 ///
@@ -49,11 +63,26 @@ void RunLLDBCommands(llvm::StringRef prefix,
 /// \param[in] commands
 ///     An array of LLDB commands to execute.
 ///
+/// \param[out] fatal_error
+///     If parsing of command directives is enabled, this variable is set to
+///     \b true if one of the commands prefixed with \b ! fails.
+///
+/// \param[in] parse_command_directives
+///     If \b false, then command prefixes like \b ! or \b ? are not parsed and
+///     each command is executed verbatim.
+///
 /// \return
 ///     A std::string that contains the prefix and all commands and
-///     command output
+///     command output.
 std::string RunLLDBCommands(llvm::StringRef prefix,
-                            const llvm::ArrayRef<std::string> &commands);
+                            const llvm::ArrayRef<std::string> &commands,
+                            bool &fatal_error,
+                            bool parse_command_directives = true);
+
+/// Similar to the method above, but without parsing command directives.
+std::string
+RunLLDBCommandsVerbatim(llvm::StringRef prefix,
+                        const llvm::ArrayRef<std::string> &commands);
 
 /// Check if a thread has a stop reason.
 ///
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index d6b593eba93ec..509b7107d9e25 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -664,7 +664,12 @@ void request_attach(const llvm::json::Object &request) {
     llvm::sys::fs::set_current_path(debuggerRoot);
 
   // Run any initialize LLDB commands the user specified in the launch.json
-  g_dap.RunInitCommands();
+  if (llvm::Error err = g_dap.RunInitCommands()) {
+    response["success"] = false;
+    EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+    g_dap.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
 
   SetSourceMapFromArguments(*arguments);
 
@@ -678,7 +683,12 @@ void request_attach(const llvm::json::Object &request) {
   }
 
   // Run any pre run LLDB commands the user specified in the launch.json
-  g_dap.RunPreRunCommands();
+  if (llvm::Error err = g_dap.RunPreRunCommands()) {
+    response["success"] = false;
+    EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+    g_dap.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
 
   if (pid == LLDB_INVALID_PROCESS_ID && wait_for) {
     char attach_msg[256];
@@ -703,7 +713,12 @@ void request_attach(const llvm::json::Object &request) {
     // We have "attachCommands" that are a set of commands that are expected
     // to execute the commands after which a process should be created. If there
     // is no valid process after running these commands, we have failed.
-    g_dap.RunLLDBCommands("Running attachCommands:", attachCommands);
+    if (llvm::Error err = g_dap.RunAttachCommands(attachCommands)) {
+      response["success"] = false;
+      EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+      g_dap.SendJSON(llvm::json::Value(std::move(response)));
+      return;
+    }
     // The custom commands might have created a new target so we should use the
     // selected target after these commands are run.
     g_dap.target = g_dap.debugger.GetSelectedTarget();
@@ -727,7 +742,7 @@ void request_attach(const llvm::json::Object &request) {
     response["success"] = llvm::json::Value(false);
     EmplaceSafeString(response, "message", std::string(error.GetCString()));
   } else {
-    g_dap.RunLLDBCommands("Running postRunCommands:", postRunCommands);
+    RunPostRunCommands(postRunCommands);
   }
 
   g_dap.SendJSON(llvm::json::Value(std::move(response)));
@@ -1270,7 +1285,8 @@ void request_evaluate(const llvm::json::Object &request) {
     if (frame.IsValid()) {
       g_dap.focus_tid = frame.GetThread().GetThreadID();
     }
-    auto result = RunLLDBCommands(llvm::StringRef(), {std::string(expression)});
+    auto result =
+        RunLLDBCommandsVerbatim(llvm::StringRef(), {std::string(expression)});
     EmplaceSafeString(body, "result", result);
     body.try_emplace("variablesReference", (int64_t)0);
   } else {
@@ -1740,7 +1756,8 @@ lldb::SBError LaunchProcess(const llvm::json::Object &request) {
     // Set the launch info so that run commands can access the configured
     // launch details.
     g_dap.target.SetLaunchInfo(launch_info);
-    g_dap.RunLLDBCommands("Running launchCommands:", launchCommands);
+    if (llvm::Error err = g_dap.RunLaunchCommands(launchCommands))
+      error.SetErrorString(llvm::toString(std::move(err)).c_str());
     // The custom commands might have created a new target so we should use the
     // selected target after these commands are run.
     g_dap.target = g_dap.debugger.GetSelectedTarget();
@@ -1820,7 +1837,12 @@ void request_launch(const llvm::json::Object &request) {
   // Run any initialize LLDB commands the user specified in the launch.json.
   // This is run before target is created, so commands can't do anything with
   // the targets - preRunCommands are run with the target.
-  g_dap.RunInitCommands();
+  if (llvm::Error err = g_dap.RunInitCommands()) {
+    response["success"] = false;
+    EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+    g_dap.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
 
   SetSourceMapFromArguments(*arguments);
 
@@ -1834,7 +1856,12 @@ void request_launch(const llvm::json::Object &request) {
   }
 
   // Run any pre run LLDB commands the user specified in the launch.json
-  g_dap.RunPreRunCommands();
+  if (llvm::Error err = g_dap.RunPreRunCommands()) {
+    response["success"] = false;
+    EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+    g_dap.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
 
   status = LaunchProcess(request);
 
@@ -1842,7 +1869,7 @@ void request_launch(const llvm::json::Object &request) {
     response["success"] = llvm::json::Value(false);
     EmplaceSafeString(response, "message", std::string(status.GetCString()));
   } else {
-    g_dap.RunLLDBCommands("Running postRunCommands:", postRunCommands);
+    RunPostRunCommands(postRunCommands);
   }
 
   g_dap.SendJSON(llvm::json::Value(std::move(response)));
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index ebb1103d695e1..68cdade443992 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -204,32 +204,32 @@
 							},
 							"initCommands": {
 								"type": "array",
-								"description": "Initialization commands executed upon debugger startup.",
+								"description": "Initialization commands executed upon debugger startup. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"preRunCommands": {
 								"type": "array",
-								"description": "Commands executed just before the program is launched.",
+								"description": "Commands executed just before the program is launched. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"postRunCommands": {
 								"type": "array",
-								"description": "Commands executed just as soon as the program is successfully launched when it's in a stopped state prior to any automatic continuation.",
+								"description": "Commands executed just as soon as the program is successfully launched when it's in a stopped state prior to any automatic continuation. If a command is prefixed with `?`, then its output is only emitted if it fails. Unlike `initCommands` or `launchCommands`, the `!` prefix is ignored.",
 								"default": []
 							},
 							"launchCommands": {
 								"type": "array",
-								"description": "Custom commands that are executed instead of launching a process. A target will be created with the launch arguments prior to executing these commands. The commands may optionally create a new target and must perform a launch. A valid process must exist after these commands complete or the \"launch\" will fail. Launch the process with \"process launch -s\" to make the process to at the entry point since lldb-dap will auto resume if necessary.",
+								"description": "Custom commands that are executed instead of launching a process. A target will be created with the launch arguments prior to executing these commands. The commands may optionally create a new target and must perform a launch. A valid process must exist after these commands complete or the \"launch\" will fail. Launch the process with \"process launch -s\" to make the process to at the entry point since lldb-dap will auto resume if necessary. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"stopCommands": {
 								"type": "array",
-								"description": "Commands executed each time the program stops.",
+								"description": "Commands executed each time the program stops. If a command is prefixed with `?`, then its output is only emitted if it fails. Unlike `initCommands` or `launchCommands`, the `!` prefix is ignored.",
 								"default": []
 							},
 							"exitCommands": {
 								"type": "array",
-								"description": "Commands executed at the end of debugging session.",
+								"description": "Commands executed at the end of debugging session. If a command is prefixed with `?`, then its output is only emitted if it fails. Unlike `initCommands` or `launchCommands`, the `!` prefix is ignored.",
 								"default": []
 							},
 							"runInTerminal": {
@@ -309,32 +309,32 @@
 							},
 							"attachCommands": {
 								"type": "array",
-								"description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands may optionally create a new target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail.",
+								"description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands may optionally create a new target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"initCommands": {
 								"type": "array",
-								"description": "Initialization commands executed upon debugger startup.",
+								"description": "Initialization commands executed upon debugger startup. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"preRunCommands": {
 								"type": "array",
-								"description": "Commands executed just before the program is attached to.",
+								"description": "Commands executed just before the program is attached to. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"postRunCommands": {
 								"type": "array",
-								"description": "Commands executed just as soon as the program is successfully attached when it's in a stopped state prior to any automatic continuation.",
+								"description": "Commands executed just as soon as the program is successfully attached when it's in a stopped state prior to any automatic continuation. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"stopCommands": {
 								"type": "array",
-								"description": "Commands executed each time the program stops.",
+								"description": "Commands executed each time the program stops. If a command is prefixed with `?`, then its output is only emitted if it fails. Unlike `initCommands` or `attachCommands`, the `!` prefix is ignored.",
 								"default": []
 							},
 							"exitCommands": {
 								"type": "array",
-								"description": "Commands executed at the end of debugging session.",
+								"description": "Commands executed at the end of debugging session. If a command is prefixed with `?`, then its output is only emitted if it fails. Unlike `initCommands` or `attachCommands`, the `!` prefix is ignored.",
 								"default": []
 							},
 							"coreFile": {



More information about the lldb-commits mailing list