Appearance
Target Pipeline Configurations β
UDE employs a decentralized configuration design where each software product owns its own pipeline parameters via an individual doc-config file β conventionally ude_doc_config.json (older projects use the name ude_config.json; both are just a JSON file passed via --doc-config). This architecture avoids a single monolithic configuration and lets teams manage their documentation pipelines alongside their codebase.
π Decentralized Configuration Strategy β
Instead of maintaining a giant system-wide map of all source repositories, each submodule or code module includes its own target configuration file. This provides several operational benefits:
- Isolated Customization: Teams can adjust collector/parser/renderer settings for their own product without affecting others.
- Local Developer Testing: Developers can compile references locally over their workspace using the same pipeline parameters as the production CI/CD environment.
- Version Control Integration: Configuration files live in the same Git branch as the source code, preventing configuration drift when API schemas change.
NOTE
There is no formal JSON-Schema file for the target config anywhere in the engine. It is consumed as an untyped Python dict via .get() calls (engine/ude/orchestrator.py), unlike the strictly-typed global config (see Global Settings). The tables below document the keys the orchestrator actually reads β not a schema the engine enforces up front.
𧬠The 3-Tier Configuration Cascade β
This is the core mechanic behind "decentralized" configuration: three JSON documents are merged into one dict before a compile runs, in a strict priority order:
In code (orchestrator.py:343):
python
config = deep_merge(deep_merge(resolved_global, sdk_config), doc_config)deep_merge(dict1, dict2) recursively merges two dicts, with dict2 winning on every leaf conflict.
CAUTION
Lists never element-merge. A list value in dict2 fully replaces the corresponding list in dict1 β it is not concatenated or appended. For example, if an SDK-level config sets "catalog_links": [{"label": "SDK Home", "url": "..."}] and a doc-config also sets "catalog_links": [...], the doc-config's list is what survives; the SDK-level entries are gone, not merged in. If you want a doc-config to add to an SDK-level list, you must repeat the SDK-level entries yourself in the doc-config's list.
Auto-discovery of the global and SDK tiers β
If no explicit --global-config / --sdk-config path is given, both are auto-discovered by walking up the directory tree from the doc-config's own directory:
find_global_config()looks forude_global_config.json, thenude_global.json.find_product_json()looks forude_sdk_config.json, thenproduct.json.
Failure behavior differs sharply depending on how the file was located:
| Situation | Behavior |
|---|---|
| Auto-discovered global or SDK config is missing or fails to parse | Logs a warning and is skipped β never fatal. |
An explicitly passed --sdk-config path fails to parse | Fatal: UdeException(f"Failed to parse SDK JSON config: {e}") |
| The doc-config itself fails to parse | Always fatal: UdeException(f"Failed to parse target JSON config {config_file_path}: {e}") |
The doc-config file (passed via --doc-config) does not exist | Fatal: UdeException(f"Configuration file {config_file_path} does not exist.") |
ποΈ The [groups] Sidebar-Taxonomy Sub-Cascade β
Nested inside the same config dict is a second, independent 3-tier merge that resolves sidebar folder taxonomy, from lowest to highest priority:
- An engine-tier default TOML per
LangΓOutputΓDefault/Legacycombination seeds the lowest priority (e.g.engine/ude/templates/SidebarStructures/default/CppHtmlDefaultRenderer.toml). - The
groupskey produced by the global β SDK β doc-config cascade above sits in the middle. - The project's own
sidebar.toml[groups]table, if present, wins at the highest priority.
The resulting config["groups"] is validated by a strict GroupsConfig Pydantic model (engine/ude/config.py:56-95, extra="forbid") β only namespace_level and class_level are permitted keys. Anything else, including a simple typo like namespac_level, raises:
text
UdeException: Invalid [groups] configuration: only 'namespace_level' and 'class_level' are permitted keys. Details: ...CAUTION
Unlike the outer 3-tier cascade (which is a plain untyped dict merge with no key validation), the [groups] result is strictly validated. A typo here is a hard failure, not a silently-ignored key.
Separately, the [[sidebar]] navigation array in sidebar.toml follows override-not-merge semantics: if the project defines its own [[sidebar]] table at all, it wholly replaces the engine-default array. Only if the project defines none does the engine default apply.
Is sidebar.toml required? β
The codebase actually contains two different loaders, with different strictness:
load_sidebar_toml()is a strict loader that raisesUdeException(f"Required sidebar.toml not found in {doc_dir}. Every document directory must contain a sidebar.toml file.")if the file is missing.resolve_config()β the path a normal compile actually goes through β calls a different, graceful loader,_load_sidebar_toml_graceful(), which simply returns{}and logs a warning ifsidebar.tomlis missing or malformed, falling back to engine defaults.
NOTE
In practice, sidebar.toml is effectively optional today: a normal ude compile run tolerates its absence and falls back to engine defaults. The stricter, mandatory-file code path (load_sidebar_toml()) exists in the codebase but is not what resolve_config() uses. Don't rely on this staying true forever β but as of this writing, treat sidebar.toml as recommended, not enforced, for a standard compile.
π Target Config Schema β Real Keys β
Below are the real keys the orchestrator reads from a doc-config, confirmed against engine/ude/orchestrator.py. This is not an exhaustive formal schema (none exists) β it's the set of keys with confirmed read/behavior, plus a smaller set of keys that are real in example configs but whose precise runtime behavior wasn't traced in this pass.
Confirmed keys with specific behavior β
src_dirβ a JSON array of source paths, required. A bare string is rejected:UdeException("'src_dir' must be a list of paths. String values are not accepted β wrap single paths in a JSON array."). Each entry is resolved relative to the doc-config's own directory.collector.language(or a top-levellanguage) β selects the language-specific Doxygen collector/parser:Cpp,Cs(also writtencsharp),Java,Py(also writtenpython).renderer.typeβ dispatch is by a generic, lowercased token match, not an arbitrary string:"html"/"static_html","legacy_html","legacy_hugo_markdown"/"legacy_markdown"/"legacy_hugo","hugo_markdown"/"markdown"/"hugo". An unmatched value raisesUdeException(f"Unsupported format '{fmt}' in target config.").IMPORTANT
Known repo inconsistency: real example configs under
ude_projects/*setrenderer.typeto a concrete renderer class name, e.g."CppHtmlDefaultRenderer"(see the class matrix in this project's core rules). Lowercased, that string does not match any of the generic dispatch tokens above. Prefer the generic tokens ("html","hugo_markdown", etc.) in new configs β those are what the engine's test suite actually exercises. Treat concrete-class-name values you find in existing example configs as a known inconsistency, not a pattern to copy.output_subdirpaired with a global/SDK-leveloutput_base_dirβ final output path is(base_dir / output_base_dir).resolve() / output_subdir. If this pair isn't set, a standaloneoutput_diron the doc-config is used as a fallback instead.cache_root_dirβ render-stage (L2) cache location for this target; falls back to the resolved output directory if unset.catalog_linksβ a list of{"label": ..., "url": ...}dicts, appended as extra links in every rendered page's footer. Remember: lists never element-merge across the cascade (see above), so a doc-config'scatalog_linksfully replaces any SDK-level list of the same name.stylesheet_dirβ HTML/Legacy-HTML asset source directory, resolved via a 4-tier fallback, first existing directory wins: (1) relative to the global config's own directory, (2) relative to the auto-discovered global config's directory, (3) the engine's own bundledtemplates/css/default, (4) relative to the doc-config's own directory.static_source_pathβ a string or list of directories searched forsidebar.tomltype = "static"custom page source files.product_name/sdk_nameβ read preferentially straight from the merged config; if absent, auto-discovered fromude_sdk_config.json/product.json.groupsβ see the[groups]sub-cascade above.
Real-world keys without traced precise behavior β
These appear in real example configs and are accepted by the orchestrator, but this documentation deliberately does not over-claim their exact internal behavior: project_name, short_project_name, static_pages_dir, copyright_start_year, incremental (bool), max_workers (int), validator.type, collector.file_patterns, collector.include_private.
π Complete Target Template (Example) β
Here is a real target configuration file from this repository, ude_projects/IGES/iges_api_cpp/ude_doc_config.json:
json
{
"project_name": "IGES C++ API Reference",
"src_dir": ["../../../sdk_sources/IGES/Include"],
"static_pages_dir": "Dev_Guides/IGES",
"copyright_start_year": 2002,
"incremental": true,
"max_workers": 8,
"collector": {"type": "CppDoxygenCollector", "file_patterns": ["**/*.h"], "include_private": false},
"parser": {"type": "CppDoxygenParser"},
"renderer": {"type": "CppHtmlDefaultRenderer"},
"output_subdir": "iges_api_cpp",
"validator": {"type": "CppCommentValidator"}
}TIP
Note that this real example uses the concrete class name "CppHtmlDefaultRenderer" for renderer.type β see the known-inconsistency callout above. If you're authoring a new config, prefer the generic token "html" (or "static_html") instead, since that's what dispatch actually matches against and what the test suite exercises.
π Relative Path Resolution β
src_dir entries, and most other path-shaped keys, are resolved relative to the doc-config file's own directory on disk β not relative to the current working directory the CLI is invoked from. This lets the same config produce identical results whether invoked from the project root, the submodule directory, or any other workspace folder.
π§ Where This Fits β
For the schema of the global config that sits at the bottom of this cascade β including error_policy, coverage_threshold, and the removed sidebar_structures_dir key β see Global Settings.
