Skip to content

Conversation

@Nerixyz
Copy link
Contributor

@Nerixyz Nerixyz commented Nov 15, 2025

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)

@llvmbot
Copy link
Member

llvmbot commented Nov 15, 2025

@llvm/pr-subscribers-lldb

Author: nerix (Nerixyz)

Changes

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)

Patch is 21.95 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/168245.diff

15 Files Affected:

  • (modified) lldb/docs/CMakeLists.txt (+7-1)
  • (added) lldb/docs/_ext/lldb_setting.py (+79)
  • (added) lldb/docs/_static/lldb-setting.css (+82)
  • (modified) lldb/docs/conf.py (+15-3)
  • (modified) lldb/docs/index.rst (+1)
  • (added) lldb/docs/use/.gitignore (+2)
  • (modified) lldb/include/lldb/Interpreter/OptionValueEnumeration.h (+2)
  • (modified) lldb/include/lldb/Interpreter/OptionValueFormatEntity.h (+3)
  • (modified) lldb/include/lldb/Interpreter/OptionValueProperties.h (+5)
  • (modified) lldb/include/lldb/Interpreter/OptionValueRegex.h (+2)
  • (modified) lldb/source/Interpreter/OptionValueFormatEntity.cpp (+4)
  • (modified) lldb/tools/lldb-test/CMakeLists.txt (+1)
  • (added) lldb/tools/lldb-test/DocumentationGenerator.cpp (+212)
  • (added) lldb/tools/lldb-test/DocumentationGenerator.h (+17)
  • (modified) lldb/tools/lldb-test/lldb-test.cpp (+17)
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;
+}
+@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 Debu...
[truncated]

@github-actions
Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions h,cpp -- lldb/tools/lldb-test/DocumentationGenerator.cpp lldb/tools/lldb-test/DocumentationGenerator.h lldb/include/lldb/Interpreter/OptionValueEnumeration.h lldb/include/lldb/Interpreter/OptionValueFormatEntity.h lldb/include/lldb/Interpreter/OptionValueProperties.h lldb/include/lldb/Interpreter/OptionValueRegex.h lldb/source/Interpreter/OptionValueFormatEntity.cpp lldb/tools/lldb-test/lldb-test.cpp --diff_from_common_commit

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/lldb/tools/lldb-test/DocumentationGenerator.cpp b/lldb/tools/lldb-test/DocumentationGenerator.cpp
index f8b24fdda..a089a7207 100644
--- a/lldb/tools/lldb-test/DocumentationGenerator.cpp
+++ b/lldb/tools/lldb-test/DocumentationGenerator.cpp
@@ -102,8 +102,8 @@ void printMdOptionFields(Stream &os, const OptionValue &val) {
        << '\n';
     break;
   case OptionValue::eTypeFormatEntity:
-    os << ":default: "
-       << val.GetAsFormatEntity()->GetEscapedDefaultFormatStr() << '\n';
+    os << ":default: " << val.GetAsFormatEntity()->GetEscapedDefaultFormatStr()
+       << '\n';
     break;
   case OptionValue::eTypeLanguage:
     os << ":default: "
@@ -143,8 +143,7 @@ void printMdOptionFields(Stream &os, const OptionValue &val) {
   case OptionValue::eTypeString: {
     llvm::StringRef default_val = val.GetAsString()->GetDefaultValueAsRef();
     if (!default_val.empty())
-      os << ":default: " << val.GetAsString()->GetDefaultValueAsRef()
-         << '\n';
+      os << ":default: " << val.GetAsString()->GetDefaultValueAsRef() << '\n';
   } break;
   default:
     break;
@@ -203,9 +202,9 @@ void printMdOptionProperties(Stream &os, uint8_t level, llvm::StringRef name,
 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);
+  StreamFile os(output_file.c_str(), File::eOpenOptionWriteOnly |
+                                         File::eOpenOptionCanCreate |
+                                         File::eOpenOptionTruncate);
   os << SETTINGS_HEADER;
   printMdOptionProperties(os, 0, "", *dbg.GetValueProperties());
   return 0;

Copy link
Member

@medismailben medismailben left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty cool! I was a bit skeptical to put the generator command in lldb-test. Why not use the main lldb binary for this ?

@Nerixyz
Copy link
Contributor Author

Nerixyz commented Nov 16, 2025

I was a bit skeptical to put the generator command in lldb-test. Why not use the main lldb binary for this ?

If I'd put it lldb itself, then any user could use something like lldb --generate-documentation <dir> and get output that's mostly internal. Maybe I'm overthinking this and that could be useful for some users as offline documentation.

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very supportive of the idea, but I have concerns with the implementation.

You say that currently that building the documentation depends on liblldb but I don't believe that's accurate (or if it is, that's a regression). The only thing we should depend on is on the SWIG wrapper (lldb.py) which only requires parsing the API headers.

Could we achieve the same thing by parsing the TableGen for the settings directly? I did something similar for generating the command line options.

@DavidSpickett
Copy link
Collaborator

You say that currently that building the documentation depends on liblldb but I don't believe that's accurate (or if it is, that's a regression). The only thing we should depend on is on the SWIG wrapper (lldb.py) which only requires parsing the API headers.

I've seen people claim this before.

#123316 is along the same lines.

@Nerixyz
Copy link
Contributor Author

Nerixyz commented Nov 17, 2025

You say that currently that building the documentation depends on liblldb but I don't believe that's accurate (or if it is, that's a regression).

I only looked at the output of the documentation builder (e.g. https://lab.llvm.org/buildbot/#/builders/45/builds/18201/steps/5/logs/stdio). This shows that liblldb is indeed built.

I'll try to do the generation with just the Tablegen files. Though I would like to have something similar for commands (a page the shows all available ones and their options). There, we'd need to convert alltheir definitions to Tablegen, I suppose.

@Nerixyz
Copy link
Contributor Author

Nerixyz commented Nov 18, 2025

Could we achieve the same thing by parsing the TableGen for the settings directly?

At least not without adding more (potentially) duplicate information about the settings hierarchy. From the TableGen files alone, we don't know the parents.
Take the C++ language plugin as an example. In the plugin properties (https://github.com/llvm/llvm-project/blob/3e8dc4dc4d04fe4c42f139423a61802b1ba719fc/lldb/source/Plugins/Language/CPlusPlus/LanguageCPlusPlusProperties.td), one setting (unction-name-format) is declared. Going just by the location in the tree, it could be in plugin.cplusplus. However, it's actually in plugin.cplusplus.display.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants