From d239ce1dc8ab10ad3bf1463d25fc6c09f60d95e0 Mon Sep 17 00:00:00 2001 From: Nerixyz 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 $ 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 """ + + 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 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 { 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 + +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 `. +Values can be added to arrays and dictionaries with `settings append -- `. + +## 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::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::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 + +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 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 + OutputDir("output-dir", + cl::desc("Directory to place the generated files in."), + cl::Required, cl::sub(GenerateDocsSubcommand)); + +} // namespace generate_docs } // namespace opts llvm::SmallVector 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;