[Lldb-commits] [lldb] [LLDB][Docs] List available settings (PR #168245)

via lldb-commits lldb-commits at lists.llvm.org
Sat Nov 15 14:08:00 PST 2025


https://github.com/Nerixyz created https://github.com/llvm/llvm-project/pull/168245

This PR adds a documentation page that lists all available settings. The page is automatically generated.

You can find a preview at https://nerixyz.github.io/test-gh-pages/use/settings.html

Having the settings listed in the online documentation makes it easier to search for users. It also has the advantage of being indexed by search engines.

To generate the list of settings, `docs-lldb-html` now depends on a target that calls `lldb-test generate-documentation`.
Some comments/questions:
- I used `lldb-test`, because all subsystems need to be initialized for the settings to be visible. `lldb-test` conveniently depends on them, and it doesn't load `.lldbinit` files. Before, you already had to build `liblldb` to build the documentation, so requiring `lldb-test` shouldn't add much more work (?).
- Because some descriptions for settings already use Markdown syntax incompatible with RST (mainly \` for code), I chose Markdown over RST.
- This is my first time using Sphinx and its customization. The `ObjectDescription` used for the `lldbsetting` directive is usually coupled with some domain and an index, but I haven't found out what their advantage would be. Should these be used here?
- The colors for the types are mostly random, but I checked that they have sufficient contrast (at least AA)

>From d239ce1dc8ab10ad3bf1463d25fc6c09f60d95e0 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Sat, 15 Nov 2025 21:57:08 +0100
Subject: [PATCH] [LLDB][Docs] List available settings

---
 lldb/docs/CMakeLists.txt                      |   8 +-
 lldb/docs/_ext/lldb_setting.py                |  79 +++++++
 lldb/docs/_static/lldb-setting.css            |  82 +++++++
 lldb/docs/conf.py                             |  18 +-
 lldb/docs/index.rst                           |   1 +
 lldb/docs/use/.gitignore                      |   2 +
 .../lldb/Interpreter/OptionValueEnumeration.h |   2 +
 .../Interpreter/OptionValueFormatEntity.h     |   3 +
 .../lldb/Interpreter/OptionValueProperties.h  |   5 +
 .../lldb/Interpreter/OptionValueRegex.h       |   2 +
 .../Interpreter/OptionValueFormatEntity.cpp   |   4 +
 lldb/tools/lldb-test/CMakeLists.txt           |   1 +
 .../lldb-test/DocumentationGenerator.cpp      | 212 ++++++++++++++++++
 lldb/tools/lldb-test/DocumentationGenerator.h |  17 ++
 lldb/tools/lldb-test/lldb-test.cpp            |  17 ++
 15 files changed, 449 insertions(+), 4 deletions(-)
 create mode 100644 lldb/docs/_ext/lldb_setting.py
 create mode 100644 lldb/docs/_static/lldb-setting.css
 create mode 100644 lldb/docs/use/.gitignore
 create mode 100644 lldb/tools/lldb-test/DocumentationGenerator.cpp
 create mode 100644 lldb/tools/lldb-test/DocumentationGenerator.h

diff --git a/lldb/docs/CMakeLists.txt b/lldb/docs/CMakeLists.txt
index f1664a6965332..8bd9a9798052f 100644
--- a/lldb/docs/CMakeLists.txt
+++ b/lldb/docs/CMakeLists.txt
@@ -36,6 +36,12 @@ if (LLDB_ENABLE_PYTHON AND SPHINX_FOUND)
 
     add_dependencies(lldb-python-doc-package swig_wrapper_python lldb-python)
 
+    add_custom_target(lldb-doc-autogen
+      COMMAND $<TARGET_FILE:lldb-test> generate-docs "--output-dir=${CMAKE_CURRENT_LIST_DIR}/use"
+      COMMENT "Generating settings documentation."
+    )
+    add_dependencies(lldb-doc-autogen lldb-test)
+
     # FIXME: Don't treat Sphinx warnings as errors. The files generated by
     # automodapi are full of warnings (partly caused by SWIG, our documentation
     # and probably also automodapi itself), so those warnings need to be fixed
@@ -51,7 +57,7 @@ if (LLDB_ENABLE_PYTHON AND SPHINX_FOUND)
     add_custom_target(clean-lldb-html COMMAND "${CMAKE_COMMAND}" -E
       remove_directory ${CMAKE_CURRENT_BINARY_DIR}/html)
     add_dependencies(docs-lldb-html swig_wrapper_python
-                     lldb-python-doc-package clean-lldb-html)
+                     lldb-python-doc-package clean-lldb-html lldb-doc-autogen)
   endif()
 
   if (${SPHINX_OUTPUT_MAN})
diff --git a/lldb/docs/_ext/lldb_setting.py b/lldb/docs/_ext/lldb_setting.py
new file mode 100644
index 0000000000000..477830869448f
--- /dev/null
+++ b/lldb/docs/_ext/lldb_setting.py
@@ -0,0 +1,79 @@
+from docutils.parsers.rst import directives
+from docutils import nodes
+
+from sphinx import addnodes
+from sphinx.application import Sphinx
+from sphinx.directives import ObjectDescription
+from sphinx.util.docfields import Field, GroupedField
+import llvm_slug
+
+
+class LiteralField(Field):
+    """A field that wraps the content in <code></code>"""
+
+    def make_field(self, types, domain, item, env=None, inliner=None, location=None):
+        fieldarg, content = item
+        fieldname = nodes.field_name("", self.label)
+        if fieldarg:
+            fieldname += nodes.Text(" ")
+            fieldname += nodes.Text(fieldarg)
+
+        fieldbody = nodes.field_body("", nodes.literal("", "", *content))
+        return nodes.field("", fieldname, fieldbody)
+
+
+# Example:
+# ```{lldbsetting} dwim-print-verbosity
+# :type: "enum"
+#
+# The verbosity level used by dwim-print.
+#
+# :enum none: Use no verbosity when running dwim-print.
+# :enum expression: Use partial verbosity when running dwim-print - display a message when `expression` evaluation is used.
+# :enum full: Use full verbosity when running dwim-print.
+# :default: none
+# ```
+class LLDBSetting(ObjectDescription):
+    option_spec = {
+        "type": directives.unchanged,
+    }
+    doc_field_types = [
+        LiteralField(
+            "default",
+            label="Default",
+            has_arg=False,
+            names=("default",),
+        ),
+        GroupedField("enum", label="Enumerations", names=("enum",)),
+        LiteralField(
+            "minimum", label="Minimum", has_arg=False, names=("min", "minimum")
+        ),
+        LiteralField(
+            "maximum", label="Maximum", has_arg=False, names=("max", "maximum")
+        ),
+    ]
+
+    def handle_signature(self, sig: str, signode: addnodes.desc_signature):
+        typ = self.options.get("type", None)
+
+        desc = addnodes.desc_name(text=sig)
+        desc += nodes.inline(
+            "",
+            typ,
+            classes=[
+                "lldb-setting-type",
+                f"lldb-setting-type-{llvm_slug.make_slug(typ)}",
+            ],
+        )
+        signode["ids"].append(sig)
+        signode += desc
+
+
+def setup(app: Sphinx):
+    app.add_directive("lldbsetting", LLDBSetting)
+
+    return {
+        "version": "0.1",
+        "parallel_read_safe": True,
+        "parallel_write_safe": True,
+    }
diff --git a/lldb/docs/_static/lldb-setting.css b/lldb/docs/_static/lldb-setting.css
new file mode 100644
index 0000000000000..5efa101c8d49f
--- /dev/null
+++ b/lldb/docs/_static/lldb-setting.css
@@ -0,0 +1,82 @@
+/* 
+  Terms use normal weight and upper case by default.
+  For settings, the term should be bold and use the original case.
+*/
+dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(
+    .simple
+  ).lldbsetting
+  .field-list
+  > dt {
+  text-transform: none;
+  font-weight: bold;
+}
+
+.lldb-setting-type {
+  --setting-color: var(--light-color);
+  --background-opacity: 0.1;
+}
+
+.lldb-setting-type {
+  float: right;
+  text-align: right;
+  text-indent: 0;
+  line-height: 1.4;
+  background-color: rgba(var(--setting-color), var(--background-opacity));
+  padding: 0px 8px;
+  border-radius: 99999px;
+  border: rgba(var(--setting-color), 1) 1px solid;
+  font-size: 0.9em;
+  color: rgba(var(--setting-color), 1);
+}
+
+body[data-theme="dark"] .lldb-setting-type {
+  --setting-color: var(--dark-color);
+  --background-opacity: 0.2;
+}
+ at media (prefers-color-scheme: dark) {
+  body[data-theme="auto"] .lldb-setting-type {
+    --setting-color: var(--dark-color);
+    --background-opacity: 0.2;
+  }
+}
+
+/* anything string-like (default) */
+.lldb-setting-type-string,
+.lldb-setting-type /* fallback */ {
+  --dark-color: 255, 177, 38;
+  --light-color: 125, 98, 1;
+}
+
+/* array-like */
+.lldb-setting-type-arguments,
+.lldb-setting-type-array,
+.lldb-setting-type-file-list {
+  --dark-color: 211, 131, 255;
+  --light-color: 64, 33, 242;
+}
+
+/* map-like */
+.lldb-setting-type-dictionary,
+.lldb-setting-type-path-map {
+  --dark-color: 243, 0, 255;
+  --light-color: 157, 0, 183;
+}
+
+/* boolean */
+.lldb-setting-type-boolean {
+  --dark-color: 29, 180, 8;
+  --light-color: 0, 123, 33;
+}
+
+/* numbers */
+.lldb-setting-type-int,
+.lldb-setting-type-unsigned {
+  --dark-color: 80, 164, 198;
+  --light-color: 1, 108, 140;
+}
+
+/* enum */
+.lldb-setting-type-enum {
+  --dark-color: 255, 87, 73;
+  --light-color: 191, 3, 10;
+}
diff --git a/lldb/docs/conf.py b/lldb/docs/conf.py
index 79cc37c8c4557..1c526ab50e9e8 100644
--- a/lldb/docs/conf.py
+++ b/lldb/docs/conf.py
@@ -12,6 +12,7 @@
 # serve to show the default.
 import sys, os, re, shutil
 from datetime import date
+from pathlib import Path
 
 # Add path for llvm_slug module.
 sys.path.insert(0, os.path.abspath(os.path.join("..", "..", "llvm", "docs")))
@@ -41,9 +42,16 @@
 # If your documentation needs a minimal Sphinx version, state it here.
 # needs_sphinx = '1.0'
 
+sys.path.append(str(Path("_ext").resolve()))
+
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ["sphinx.ext.todo", "sphinx.ext.mathjax", "sphinx.ext.intersphinx"]
+extensions = [
+    "sphinx.ext.todo",
+    "sphinx.ext.mathjax",
+    "sphinx.ext.intersphinx",
+    "lldb_setting",
+]
 
 # When building man pages, we do not use the markdown pages,
 # So, we can continue without the myst_parser dependencies.
@@ -59,6 +67,7 @@
 # Automatic anchors for markdown titles
 myst_heading_anchors = 6
 myst_heading_slug_func = "llvm_slug.make_slug"
+myst_enable_extensions = ["fieldlist"]
 
 autodoc_default_options = {"special-members": True}
 
@@ -132,7 +141,7 @@
 # included by any doctree (as the manpage ignores those pages but they are
 # potentially still around from a previous website generation).
 if building_man_page:
-    exclude_patterns.append(automodapi_toctreedirnm)
+    exclude_patterns += [automodapi_toctreedirnm, "use/settings.md"]
 # Use the recommended 'any' rule so that referencing SB API classes is possible
 # by just writing `SBData`.
 default_role = "any"
@@ -192,7 +201,10 @@
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ["_static"]
+html_static_path = ["_static"]
+html_css_files = [
+    "lldb-setting.css",
+]
 
 html_extra_path = [".htaccess"]
 
diff --git a/lldb/docs/index.rst b/lldb/docs/index.rst
index a981c0ab8d6e9..c1635984887a3 100644
--- a/lldb/docs/index.rst
+++ b/lldb/docs/index.rst
@@ -137,6 +137,7 @@ interesting areas to contribute to lldb.
    use/aarch64-linux
    use/symbolfilejson
    use/mcp
+   use/settings
    use/troubleshooting
    use/links
    Man Page <man/lldb>
diff --git a/lldb/docs/use/.gitignore b/lldb/docs/use/.gitignore
new file mode 100644
index 0000000000000..e9c6c7212fad2
--- /dev/null
+++ b/lldb/docs/use/.gitignore
@@ -0,0 +1,2 @@
+# autogenerated
+/settings.md
diff --git a/lldb/include/lldb/Interpreter/OptionValueEnumeration.h b/lldb/include/lldb/Interpreter/OptionValueEnumeration.h
index 91ab454b2065e..7c38599746419 100644
--- a/lldb/include/lldb/Interpreter/OptionValueEnumeration.h
+++ b/lldb/include/lldb/Interpreter/OptionValueEnumeration.h
@@ -70,6 +70,8 @@ class OptionValueEnumeration
 
   void SetDefaultValue(enum_type value) { m_default_value = value; }
 
+  const EnumerationMap &Enumerations() const { return m_enumerations; }
+
 protected:
   void SetEnumerations(const OptionEnumValues &enumerators);
   void DumpEnum(Stream &strm, enum_type value);
diff --git a/lldb/include/lldb/Interpreter/OptionValueFormatEntity.h b/lldb/include/lldb/Interpreter/OptionValueFormatEntity.h
index c10d56cbeb70b..cf60bfb0e4c6d 100644
--- a/lldb/include/lldb/Interpreter/OptionValueFormatEntity.h
+++ b/lldb/include/lldb/Interpreter/OptionValueFormatEntity.h
@@ -45,6 +45,9 @@ class OptionValueFormatEntity
 
   const FormatEntity::Entry &GetDefaultValue() const { return m_default_entry; }
 
+  llvm::StringRef GetDefaultFormatStr() const { return m_default_format; }
+  std::string GetEscapedDefaultFormatStr() const;
+
 protected:
   std::string m_current_format;
   std::string m_default_format;
diff --git a/lldb/include/lldb/Interpreter/OptionValueProperties.h b/lldb/include/lldb/Interpreter/OptionValueProperties.h
index 91a3955962372..022f5b7753b2e 100644
--- a/lldb/include/lldb/Interpreter/OptionValueProperties.h
+++ b/lldb/include/lldb/Interpreter/OptionValueProperties.h
@@ -82,6 +82,11 @@ class OptionValueProperties
     return ProtectedGetPropertyAtIndex(idx);
   }
 
+  virtual size_t
+  GetNumProperties(const ExecutionContext *exe_ctx = nullptr) const {
+    return m_properties.size();
+  }
+
   // Property can be a property path like
   // "target.process.extra-startup-command"
   virtual const Property *
diff --git a/lldb/include/lldb/Interpreter/OptionValueRegex.h b/lldb/include/lldb/Interpreter/OptionValueRegex.h
index b952cb2476012..209d9ccae5ce2 100644
--- a/lldb/include/lldb/Interpreter/OptionValueRegex.h
+++ b/lldb/include/lldb/Interpreter/OptionValueRegex.h
@@ -55,6 +55,8 @@ class OptionValueRegex : public Cloneable<OptionValueRegex, OptionValue> {
 
   bool IsValid() const { return m_regex.IsValid(); }
 
+  llvm::StringRef GetDefaultValue() const { return m_default_regex_str; }
+
 protected:
   RegularExpression m_regex;
   std::string m_default_regex_str;
diff --git a/lldb/source/Interpreter/OptionValueFormatEntity.cpp b/lldb/source/Interpreter/OptionValueFormatEntity.cpp
index b31dd4e475878..873c91b1ae8ef 100644
--- a/lldb/source/Interpreter/OptionValueFormatEntity.cpp
+++ b/lldb/source/Interpreter/OptionValueFormatEntity.cpp
@@ -122,3 +122,7 @@ void OptionValueFormatEntity::AutoComplete(CommandInterpreter &interpreter,
                                            CompletionRequest &request) {
   FormatEntity::AutoComplete(request);
 }
+
+std::string OptionValueFormatEntity::GetEscapedDefaultFormatStr() const {
+  return EscapeBackticks(m_default_format);
+}
diff --git a/lldb/tools/lldb-test/CMakeLists.txt b/lldb/tools/lldb-test/CMakeLists.txt
index 9d85cb8f8d168..eb1a4c1fc8d6b 100644
--- a/lldb/tools/lldb-test/CMakeLists.txt
+++ b/lldb/tools/lldb-test/CMakeLists.txt
@@ -1,6 +1,7 @@
 get_property(LLDB_ALL_PLUGINS GLOBAL PROPERTY LLDB_PLUGINS)
 
 add_lldb_tool(lldb-test
+  DocumentationGenerator.cpp
   FormatUtil.cpp
   lldb-test.cpp
   SystemInitializerTest.cpp
diff --git a/lldb/tools/lldb-test/DocumentationGenerator.cpp b/lldb/tools/lldb-test/DocumentationGenerator.cpp
new file mode 100644
index 0000000000000..f8b24fdda6cc0
--- /dev/null
+++ b/lldb/tools/lldb-test/DocumentationGenerator.cpp
@@ -0,0 +1,212 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DocumentationGenerator.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Core/Module.h"
+#include "lldb/DataFormatters/FormatManager.h"
+#include "lldb/Interpreter/OptionValueArch.h"
+#include "lldb/Interpreter/OptionValueBoolean.h"
+#include "lldb/Interpreter/OptionValueChar.h"
+#include "lldb/Interpreter/OptionValueEnumeration.h"
+#include "lldb/Interpreter/OptionValueFileSpec.h"
+#include "lldb/Interpreter/OptionValueFormat.h"
+#include "lldb/Interpreter/OptionValueFormatEntity.h"
+#include "lldb/Interpreter/OptionValueLanguage.h"
+#include "lldb/Interpreter/OptionValueProperties.h"
+#include "lldb/Interpreter/OptionValueRegex.h"
+#include "lldb/Interpreter/OptionValueSInt64.h"
+#include "lldb/Interpreter/OptionValueString.h"
+#include "lldb/Interpreter/OptionValueUInt64.h"
+#include "lldb/Target/Language.h"
+#include "lldb/Utility/DataExtractor.h"
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/WithColor.h"
+
+#include <cstdio>
+
+namespace {
+
+using namespace lldb;
+using namespace lldb_private;
+
+constexpr llvm::StringRef SETTINGS_HEADER = R"(
+# Settings
+
+This page lists all available settings in LLDB.
+Settings can be set using `settings set <name> <value>`.
+Values can be added to arrays and dictionaries with `settings append -- <name> <value>`.
+
+## Root
+)";
+
+/// Print the fields for one option value.
+/// These fields are at the bottom of the directive.
+void printMdOptionFields(Stream &os, const OptionValue &val) {
+  switch (val.GetType()) {
+  case OptionValue::eTypeArch:
+    os << ":default: "
+       << val.GetAsArch()->GetDefaultValue().GetArchitectureName() << '\n';
+    break;
+  case OptionValue::eTypeBoolean:
+    os << ":default: ";
+    os.PutCString(val.GetAsBoolean()->GetDefaultValue() ? "true" : "false");
+    os << '\n';
+    break;
+  case OptionValue::eTypeChar:
+    os << ":default: " << val.GetAsChar()->GetCurrentValue() << '\n';
+    break;
+  case OptionValue::eTypeEnum: {
+    const auto &enumerations = val.GetAsEnumeration()->Enumerations();
+    auto default_value = val.GetAsEnumeration()->GetDefaultValue();
+    llvm::StringRef default_str;
+
+    for (const auto &entry : enumerations) {
+      os << ":enum " << entry.cstring << ": ";
+      if (entry.value.description)
+        os << entry.value.description;
+      os << '\n';
+      if (entry.value.value == default_value)
+        default_str = entry.cstring;
+    }
+
+    if (!default_str.empty())
+      os << ":default: " << default_str << '\n';
+  } break;
+  case OptionValue::eTypeFileSpec: {
+    std::string path = val.GetAsFileSpec()->GetDefaultValue().GetPath(false);
+
+    // Some defaults include the user's home directory. This should show as '~'
+    // in the documentation.
+    llvm::SmallString<64> user_home_dir;
+    if (FileSystem::Instance().GetHomeDirectory(user_home_dir)) {
+      std::string home_path = FileSpec(user_home_dir.c_str()).GetPath(false);
+      if (llvm::StringRef(path).starts_with(home_path))
+        path.replace(0, user_home_dir.size(), "~");
+    }
+
+    if (!path.empty())
+      os << ":default: " << path << '\n';
+  } break;
+  case OptionValue::eTypeFormat:
+    os << ":default: "
+       << FormatManager::GetFormatAsCString(
+              val.GetAsFormat()->GetCurrentValue())
+       << '\n';
+    break;
+  case OptionValue::eTypeFormatEntity:
+    os << ":default: "
+       << val.GetAsFormatEntity()->GetEscapedDefaultFormatStr() << '\n';
+    break;
+  case OptionValue::eTypeLanguage:
+    os << ":default: "
+       << Language::GetNameForLanguageType(
+              val.GetAsLanguage()->GetDefaultValue())
+       << '\n';
+    break;
+  case OptionValue::eTypeRegex:
+    os << ":default: " << val.GetAsRegex()->GetDefaultValue() << '\n';
+    break;
+  case OptionValue::eTypeSInt64: {
+    os << ":default: "
+       << llvm::formatv("{}", val.GetAsSInt64()->GetDefaultValue()).str()
+       << '\n';
+
+    int64_t min = val.GetAsSInt64()->GetMinimumValue();
+    if (min != 0)
+      os << ":minimum: " << llvm::formatv("{}", min).str() << '\n';
+
+    int64_t max = val.GetAsSInt64()->GetMaximumValue();
+    if (max != std::numeric_limits<int64_t>::max())
+      os << ":maximum: " << llvm::formatv("{}", max).str() << '\n';
+  } break;
+  case OptionValue::eTypeUInt64: {
+    os << ":default: "
+       << llvm::formatv("{}", val.GetAsUInt64()->GetDefaultValue()).str()
+       << '\n';
+
+    uint64_t min = val.GetAsUInt64()->GetMinimumValue();
+    if (min != 0)
+      os << ":minimum: " << llvm::formatv("{}", min).str() << '\n';
+
+    uint64_t max = val.GetAsUInt64()->GetMaximumValue();
+    if (max != std::numeric_limits<uint64_t>::max())
+      os << ":maximum: " << llvm::formatv("{}", max).str() << '\n';
+  } break;
+  case OptionValue::eTypeString: {
+    llvm::StringRef default_val = val.GetAsString()->GetDefaultValueAsRef();
+    if (!default_val.empty())
+      os << ":default: " << val.GetAsString()->GetDefaultValueAsRef()
+         << '\n';
+  } break;
+  default:
+    break;
+  }
+}
+
+void printMdOptionValueProperty(Stream &os, llvm::StringRef prefix,
+                                const Property &prop) {
+  OptionValueSP value_sp = prop.GetValue();
+  if (!value_sp || value_sp->GetType() == OptionValue::eTypeProperties)
+    return;
+
+  os << "```{lldbsetting} ";
+  if (!prefix.empty())
+    os << prefix << '.';
+  os << prop.GetName() << '\n';
+  os << ":type: \"" << value_sp->GetTypeAsCString() << "\"\n\n";
+
+  os << prop.GetDescription().trim() << "\n\n";
+
+  printMdOptionFields(os, *value_sp);
+  os << "```\n";
+}
+
+void printMdOptionProperties(Stream &os, uint8_t level, llvm::StringRef name,
+                             const OptionValueProperties &props) {
+
+  if (level > 0)
+    os << std::string(level + 2, '#') << ' ' << props.GetName() << "\n\n";
+
+  for (size_t i = 0; i < props.GetNumProperties(); i++) {
+    const Property *prop = props.GetPropertyAtIndex(i);
+    if (prop)
+      printMdOptionValueProperty(os, name, *prop);
+  }
+
+  // put properties last
+  for (size_t i = 0; i < props.GetNumProperties(); i++) {
+    const Property *prop = props.GetPropertyAtIndex(i);
+    if (!prop || !prop->GetValue() ||
+        prop->GetValue()->GetType() != OptionValue::eTypeProperties)
+      continue;
+
+    std::string full_path;
+    if (level > 0)
+      full_path = name.str() + '.';
+    full_path.append(prop->GetName());
+
+    printMdOptionProperties(os, level + 1, full_path,
+                            *prop->GetValue()->GetAsProperties());
+  }
+}
+
+} // namespace
+
+int lldb_private::generateMarkdownDocs(Debugger &dbg,
+                                       llvm::StringRef output_dir) {
+  std::string output_file = (output_dir + "/settings.md").str();
+  StreamFile os(output_file.c_str(),
+               File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate |
+                   File::eOpenOptionTruncate);
+  os << SETTINGS_HEADER;
+  printMdOptionProperties(os, 0, "", *dbg.GetValueProperties());
+  return 0;
+}
diff --git a/lldb/tools/lldb-test/DocumentationGenerator.h b/lldb/tools/lldb-test/DocumentationGenerator.h
new file mode 100644
index 0000000000000..5c8e7fcb73942
--- /dev/null
+++ b/lldb/tools/lldb-test/DocumentationGenerator.h
@@ -0,0 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <llvm/ADT/StringRef.h>
+
+namespace lldb_private {
+
+class Debugger;
+
+int generateMarkdownDocs(Debugger &dbg, llvm::StringRef output_dir);
+
+} // namespace lldb_private
diff --git a/lldb/tools/lldb-test/lldb-test.cpp b/lldb/tools/lldb-test/lldb-test.cpp
index 3f198d963a93b..5773ba9a4318f 100644
--- a/lldb/tools/lldb-test/lldb-test.cpp
+++ b/lldb/tools/lldb-test/lldb-test.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "DocumentationGenerator.h"
 #include "FormatUtil.h"
 #include "SystemInitializerTest.h"
 
@@ -66,6 +67,10 @@ cl::SubCommand SymTabSubcommand("symtab",
 cl::SubCommand IRMemoryMapSubcommand("ir-memory-map", "Test IRMemoryMap");
 cl::SubCommand AssertSubcommand("assert", "Test assert handling");
 
+static cl::SubCommand
+    GenerateDocsSubcommand("generate-docs",
+                           "Generate markdown documentation from settings");
+
 cl::opt<std::string> Log("log", cl::desc("Path to a log file"), cl::init(""),
                          cl::sub(BreakpointSubcommand),
                          cl::sub(ObjectFileSubcommand),
@@ -300,6 +305,15 @@ int evaluateMemoryMapCommands(Debugger &Dbg);
 namespace assert {
 int lldb_assert(Debugger &Dbg);
 } // namespace assert
+
+namespace generate_docs {
+
+static cl::opt<std::string>
+    OutputDir("output-dir",
+              cl::desc("Directory to place the generated files in."),
+              cl::Required, cl::sub(GenerateDocsSubcommand));
+
+} // namespace generate_docs
 } // namespace opts
 
 llvm::SmallVector<CompilerContext, 4> parseCompilerContext() {
@@ -1284,6 +1298,9 @@ int main(int argc, const char *argv[]) {
     return opts::irmemorymap::evaluateMemoryMapCommands(*Dbg);
   if (opts::AssertSubcommand)
     return opts::assert::lldb_assert(*Dbg);
+  if (opts::GenerateDocsSubcommand)
+    return lldb_private::generateMarkdownDocs(*Dbg,
+                                              opts::generate_docs::OutputDir);
 
   WithColor::error() << "No command specified.\n";
   return 1;



More information about the lldb-commits mailing list