From 17851a2fc77ac37a83fbd47bfc42015b5bdeccbd Mon Sep 17 00:00:00 2001 From: Jan Schlosser Date: Tue, 18 Aug 2026 08:56:51 +0200 Subject: [PATCH] fix(sphinx): stop silently dropping architectural_design docs Two related bugs caused a dependable_element's `architectural_design` docs (e.g. static_design/private_api/public_api diagrams and any Markdown design write-ups) to silently disappear from a consumer's built Sphinx site, even though their toctree entries were still generated: 1. `sphinx_conf_helpers.DEFAULT_EXCLUDE_PATTERNS` used the glob `**/*_design`, intended to exclude generated unit-design include-fragments (`units/unit_1_design/`, always named `"units/" + unit_name + "_design"` by dependable_element.bzl). The glob also matched any directory that merely *ends* with "_design", including `architectural_design/` and `software_architectural_design/` themselves -- i.e. the actual architecture documentation. Narrowed the pattern to `**/units/*_design` so it only targets the intended fragment dirs. 2. Once no longer excluded, the generated RST wrapper for each PlantUML diagram (declared under `{architectural_design_target_name}/`) and the diagram's raw `.puml` source (which typically lives directly in the target's package, one directory above) ended up staged in different directories by dependable_element.bzl's common-directory flattening logic (`_find_common_directory`/`_compute_relative_path`), breaking the wrapper's same-directory `.. uml:: .puml` reference ("PlantUML file ... cannot be read"). Fixed by symlinking each diagram alongside its generated wrapper (`_colocate_puml_with_wrapper` in architectural_design.bzl) so they are always siblings regardless of the diagram's original location. Both bugs were latent for every `dependable_element` consumer using `architectural_design()`, but only became build-breaking once (1) was fixed, since (1) previously hid the effects of (2) as well. Verified against eclipse-score/communication (Message Passing and mw::com dependable elements): both `software_arch` pages now render their design write-ups and PlantUML diagrams, and the Sphinx build still passes with `-W` (warnings-as-errors). --- .../private/architectural_design.bzl | 56 ++++++++++++++++++- .../rules_score/src/sphinx_conf_helpers.py | 12 ++-- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/bazel/rules/rules_score/private/architectural_design.bzl b/bazel/rules/rules_score/private/architectural_design.bzl index c55f15b9..38a9c197 100644 --- a/bazel/rules/rules_score/private/architectural_design.bzl +++ b/bazel/rules/rules_score/private/architectural_design.bzl @@ -108,6 +108,49 @@ def _parse_puml_diagrams(ctx, files): idmap_outputs.append(idmap) return fbs_outputs, lobster_outputs, idmap_outputs +def _colocate_puml_with_wrapper(ctx, puml_files, output_dir): + """Symlink .puml/.plantuml sources next to their generated RST wrapper. + + make_puml_rst_wrappers() declares each wrapper at + "{output_dir}/{stem}.rst" (output_dir is this target's ctx.label.name) + and embeds the diagram via a same-directory sibling reference + (``.. uml:: {basename}``). The .puml source itself, however, usually + lives directly in this target's package -- one directory above + `output_dir` -- not nested under it. When dependable_element.bzl later + stages every SphinxSourcesInfo file for the HTML build, it flattens paths + based on the *shortest common directory* across all of a label's files + (see its `_find_common_directory`); mixing a file that sits directly in + the package with one nested one level deeper collapses the common + directory to the package itself, so the wrapper ends up staged one + level deeper than the raw .puml file and the `.. uml::` sibling + reference breaks (PlantUML file "x.puml" cannot be read). Symlinking a + same-named copy of every diagram alongside its wrapper keeps them + siblings under `output_dir` regardless of the diagram's original + on-disk location, so downstream flattening logic stages them together. + + Args: + ctx: Rule context. + puml_files: Iterable of File objects; non-.puml/.plantuml files are + passed through unchanged. + output_dir: String prefix matching the one passed to + make_puml_rst_wrappers() (typically ctx.label.name). + + Returns: + List of File objects with .puml/.plantuml entries replaced by + same-directory symlinked copies. + """ + colocated = [] + for f in puml_files: + if f.extension not in ("puml", "plantuml"): + colocated.append(f) + continue + copy = ctx.actions.declare_file( + "{}/{}".format(output_dir, f.basename), + ) + ctx.actions.symlink(output = copy, target_file = f) + colocated.append(copy) + return colocated + def _run_validation(ctx, component_fbs_files, sequence_fbs_files, public_api_fbs_files, internal_api_fbs_files): """Run the architectural-design validation profile. @@ -168,9 +211,18 @@ def _architectural_design_impl(ctx): internal_api_fbs = depset(internal_api_fbs_list) public_api_lobster = depset(public_api_lobster_list) - # Source files for SphinxSourcesInfo (sphinx documentation pipeline) + # Source files for SphinxSourcesInfo (sphinx documentation pipeline). + # .puml/.plantuml sources are colocated (symlinked) next to their + # generated RST wrapper -- see _colocate_puml_with_wrapper for why this + # is required for the `.. uml::` sibling reference to resolve once + # dependable_element.bzl stages these files for the HTML build. all_source_files = depset( - transitive = [depset(ctx.files.static), depset(ctx.files.dynamic), depset(ctx.files.public_api), depset(ctx.files.internal_api)], + transitive = [ + depset(_colocate_puml_with_wrapper(ctx, ctx.files.static, ctx.label.name)), + depset(_colocate_puml_with_wrapper(ctx, ctx.files.dynamic, ctx.label.name)), + depset(_colocate_puml_with_wrapper(ctx, ctx.files.public_api, ctx.label.name)), + depset(_colocate_puml_with_wrapper(ctx, ctx.files.internal_api, ctx.label.name)), + ], ) # All idmap sidecars (across static/dynamic/public_api/internal_api) are diff --git a/bazel/rules/rules_score/src/sphinx_conf_helpers.py b/bazel/rules/rules_score/src/sphinx_conf_helpers.py index f256bd79..db3fad39 100644 --- a/bazel/rules/rules_score/src/sphinx_conf_helpers.py +++ b/bazel/rules/rules_score/src/sphinx_conf_helpers.py @@ -54,10 +54,14 @@ DEFAULT_EXCLUDE_PATTERNS: List[str] = [ "bazel-*", ".venv*", - # Design-fragment subdirectories (e.g. units/unit_1_design/) are included - # via '.. include::' directives and must not be treated as standalone - # pages. - "**/*_design", + # Design-fragment subdirectories (e.g. units/unit_1_design/, always named + # "units/" + unit_name + "_design" by dependable_element.bzl) are + # included via '.. include::' directives and must not be treated as + # standalone pages. Scoped to the "units/" parent so it doesn't also + # match unrelated directories that merely end in "_design", such as + # architectural_design/ or software_architectural_design/ (the actual + # architecture documentation, which must be built as standalone pages). + "**/units/*_design", ] # The needs builder phase runs against only the static docs/ checkout;