Skip to content

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 for ude_global_config.json, then ude_global.json.
  • find_product_json() looks for ude_sdk_config.json, then product.json.

Failure behavior differs sharply depending on how the file was located:

SituationBehavior
Auto-discovered global or SDK config is missing or fails to parseLogs a warning and is skipped β€” never fatal.
An explicitly passed --sdk-config path fails to parseFatal: UdeException(f"Failed to parse SDK JSON config: {e}")
The doc-config itself fails to parseAlways fatal: UdeException(f"Failed to parse target JSON config {config_file_path}: {e}")
The doc-config file (passed via --doc-config) does not existFatal: 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:

  1. An engine-tier default TOML per LangΓ—OutputΓ—Default/Legacy combination seeds the lowest priority (e.g. engine/ude/templates/SidebarStructures/default/CppHtmlDefaultRenderer.toml).
  2. The groups key produced by the global β†’ SDK β†’ doc-config cascade above sits in the middle.
  3. 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 raises UdeException(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 if sidebar.toml is 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-level language) β€” selects the language-specific Doxygen collector/parser: Cpp, Cs (also written csharp), Java, Py (also written python).

  • 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 raises UdeException(f"Unsupported format '{fmt}' in target config.").

    IMPORTANT

    Known repo inconsistency: real example configs under ude_projects/* set renderer.type to 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_subdir paired with a global/SDK-level output_base_dir β€” final output path is (base_dir / output_base_dir).resolve() / output_subdir. If this pair isn't set, a standalone output_dir on 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's catalog_links fully 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 bundled templates/css/default, (4) relative to the doc-config's own directory.

  • static_source_path β€” a string or list of directories searched for sidebar.toml type = "static" custom page source files.

  • product_name / sdk_name β€” read preferentially straight from the merged config; if absent, auto-discovered from ude_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.