diff --git a/MODULE.bazel b/MODULE.bazel index 9c08d4aa..0c20a38e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -291,14 +291,14 @@ use_repo(pip, "manual_analysis_deps") bazel_dep(name = "trlc", version = "0.0.0") git_override( module_name = "trlc", - commit = "7f06d0396fd0e5f02b657d25700775af5113b7e0", + commit = "c4c531b9d667085daa09dfc1590edacc314bfda4", remote = "https://github.com/bmw-software-engineering/trlc.git", ) bazel_dep(name = "lobster", version = "0.0.0") git_override( module_name = "lobster", - commit = "9da4a491ef65c33f50e82d1e29346180dd5acc60", + commit = "2792e2daee2cf524fdc7b1545fd3537791ebc36c", remote = "https://github.com/bmw-software-engineering/lobster.git", ) diff --git a/bazel/rules/rules_score/BUILD b/bazel/rules/rules_score/BUILD index fdf12d19..08ff2788 100644 --- a/bazel/rules/rules_score/BUILD +++ b/bazel/rules/rules_score/BUILD @@ -155,6 +155,7 @@ sphinx_module( "//plantuml/sphinx/clickable_plantuml:readme_md": "bazel/rules/rules_score/docs/tool_reference/clickable_plantuml.md", "//lobster_bazel:readme_md": "bazel/rules/rules_score/docs/tool_reference/lobster_bazel.md", "//manual_analysis:readme_md": "bazel/rules/rules_score/docs/tool_reference/manual_analysis.md", + "//validation/core/docs/requirements:requirements_rst": "bazel/rules/rules_score/docs/requirements/validation_core_requirements_rst.rst", }, visibility = ["//visibility:public"], ) diff --git a/bazel/rules/rules_score/docs/rule_reference.rst b/bazel/rules/rules_score/docs/rule_reference.rst index b2e90a3d..ceca4bf1 100644 --- a/bazel/rules/rules_score/docs/rule_reference.rst +++ b/bazel/rules/rules_score/docs/rule_reference.rst @@ -72,7 +72,10 @@ cross-module dependencies and automatic HTML merging. - no - Bazel visibility -**Generated targets:** ```` — Sphinx build; output under ``bazel-bin//html/`` +**Generated targets:** + +- ```` — HTML output under ``bazel-bin//html/``; use in ``deps`` of other ``sphinx_module`` targets. +- ``_needs`` — ``needs.json`` produced by the needs builder; consumed transitively by downstream modules for cross-module ``{requirement:downstream-ref}`` resolution. See :ref:`two-phase-sphinx-build` for the full data-flow description. Artifact Rules diff --git a/bazel/rules/rules_score/docs/tool_qualification.rst b/bazel/rules/rules_score/docs/tool_qualification.rst index 3ea5015e..0fba2557 100644 --- a/bazel/rules/rules_score/docs/tool_qualification.rst +++ b/bazel/rules/rules_score/docs/tool_qualification.rst @@ -35,3 +35,4 @@ Requirements Use Cases Potential Errors Tool Requirements + Validation Core Requirements diff --git a/bazel/rules/rules_score/docs/tooling_architecture.rst b/bazel/rules/rules_score/docs/tooling_architecture.rst index 6245782d..e5b0621c 100644 --- a/bazel/rules/rules_score/docs/tooling_architecture.rst +++ b/bazel/rules/rules_score/docs/tooling_architecture.rst @@ -123,11 +123,20 @@ are rendered under :doc:`tool_reference/index`. * - **Sphinx (Docs)** - ``score_build`` (``src/sphinx_wrapper.py``), ``html_merge_tool`` (``src/sphinx_html_merge.py``), - ``sphinx_module_ext`` / ``bazel_sphinx_needs`` + ``sphinx_module_ext``, + ``trlc`` Sphinx extension (``@trlc``) - ``sphinx_module``, ``dependable_element`` - - Two-phase documentation build: phase 1 emits ``needs.json`` for - sphinx-needs cross-referencing, phase 2 builds HTML and merges - dependency modules into one site. + - Two-phase documentation build: **phase 1** (``_needs`` target) + runs Sphinx with ``--builder needs`` to emit ``needs.json`` containing + any native ``sphinx-needs`` (``.. need::``) directives found in the + sources. **Phase 2** (```` target) runs Sphinx with + ``--builder html``, resolving ``trlc`` ``.. requirement:definition::`` + cross-references within the relocated source tree and consuming + ``needs.json`` files of all ``deps`` via ``needs_external_needs_json`` + for cross-module ``sphinx-needs`` links. + ``src/sphinx_html_merge.py`` then merges dependency HTML directories + into the final output site. + See :ref:`two-phase-sphinx-build` for details. * - **Lobster Bazel** - ``//lobster_bazel:lobster_linker`` (``parse_source_files.py``) - ``rules_score_impl`` (tool-qualification chain) @@ -169,3 +178,86 @@ feed that pipeline: * **FMEA** (``failuremodes.trlc`` / ``controlmeasures.trlc``) → ``lobster-trlc``; **FTA** (``fta.puml``) → ``safety_analysis_tools`` → ``root_causes.lobster``. * **Unit tests** (gtest) → ``gtest_report`` → ``.lobster``. + +.. _two-phase-sphinx-build: + +Two-phase Sphinx build +---------------------- + +Every ``sphinx_module`` call expands into **two** Bazel targets that run +sequentially: + +.. code-block:: text + + _needs (phase 1 — needs builder) + (phase 2 — HTML builder) + +Phase 1 — ``_needs`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sphinx is invoked with ``--builder needs`` against the **static docs/ source +tree** (only ``srcs`` files — the checked-in ``.rst``/``.md`` files plus the +generated ``trlc_rst`` outputs that are listed as label targets in ``srcs``). +Generated/external files from ``renamed_srcs`` and ``docs_library_deps`` are +**not** included; their toctree entries produce ``toc.not_readable`` warnings +that are suppressed in ``conf.template.py`` (see below for why this is safe). + +The needs builder scans every document for ``.. need::`` directives +(``sphinx-needs`` native format) and writes them to a ``needs.json`` file. + +The ``toc.not_readable`` suppression in ``conf.template.py`` is safe for the +HTML phase because that phase relocates every file into a staging directory, so +it never encounters an unresolvable toctree entry. + +The resulting ``needs.json`` (empty for modules whose requirements are authored +in TRLC rather than native ``sphinx-needs`` format) is wrapped in a +``SphinxNeedsInfo`` provider and propagated transitively so that every +downstream module can consume it. + +Phase 2 — ```` +~~~~~~~~~~~~~~~~~~~~ + +Sphinx is invoked with ``--builder html`` against a **relocated copy** of all +source files (``srcs``, ``renamed_srcs``, ``docs_library_deps``) symlinked into +a unified staging directory under ``bazel-bin/``. + +This is also where ``.. requirement:definition::`` directives (from the ``trlc`` +Sphinx extension) are processed and cross-references resolved. The raw +requirement records come from ``.trlc`` source files compiled by the +``trlc_rst`` Bazel rule into ``.rst`` files that contain the directives. The +chain is: + +.. code-block:: text + + *.trlc + └─ trlc_rst (Bazel rule, @trlc) + └─ requirements_rst.rst (.. requirement:definition:: ...) + └─ Sphinx HTML builder (resolves {requirement:downstream-ref}) + +Before the HTML build starts, ``sphinx_module_ext.py`` reads the aggregated +``needs_external_needs.json`` +(written by the Bazel rule from all incoming ``SphinxNeedsInfo`` providers) and +populates the ``needs_external_needs`` Sphinx configuration key. This tells +``sphinx-needs`` where to find the ``needs.json`` of each dependency and what +base URL to use for generated hyperlinks, so a ``{requirement:downstream-ref}`` +role in a spec file can link directly to the requirement definition page in the +dependency's HTML. + +After the HTML build, ``src/sphinx_html_merge.py`` copies each dependency's +output directory into ``/html//`` so the final site is +self-contained. + +.. code-block:: text + + deps[*].needs.json ──► needs_external_needs.json + │ + sources (all relocated) ───────┤ + ▼ + Sphinx HTML builder + │ + /_html/ ──► sphinx_html_merge + │ + /html/ + ├── index.html + ├── dep1/ ← merged + └── dep2/ ← merged diff --git a/bazel/rules/rules_score/lobster/config/lobster_comp_req.yaml b/bazel/rules/rules_score/lobster/config/lobster_comp_req.yaml index 0356a672..3c1517f6 100644 --- a/bazel/rules/rules_score/lobster/config/lobster_comp_req.yaml +++ b/bazel/rules/rules_score/lobster/config/lobster_comp_req.yaml @@ -18,6 +18,11 @@ to-string-rules: to-string: - $(item)@$(version) - $(item) + - package: ScoreReq + tuple-type: CompReqSourceId + to-string: + - $(item)@$(version) + - $(item) conversion-rules: - package: ScoreReq record-type: CompReq diff --git a/bazel/rules/rules_score/private/sphinx_module.bzl b/bazel/rules/rules_score/private/sphinx_module.bzl index 15b4527d..6fae4449 100644 --- a/bazel/rules/rules_score/private/sphinx_module.bzl +++ b/bazel/rules/rules_score/private/sphinx_module.bzl @@ -84,7 +84,15 @@ def _score_needs_impl(ctx): # Get config file (generate or use provided) config_file = _create_config_py(ctx) - # Phase 1: Build needs.json (without external needs) + # Phase 1: Build needs.json (without external needs). + # The needs builder (sphinx-needs NeedsBuilder) only collects `.. need::` + # directives — it is blind to the custom trlc `RequirementsDomain` and its + # `.. requirement:definition::` directives. Generated/external files + # (renamed_srcs, docs_library_deps) are therefore not needed here. Their + # toctree entries would produce toc.not_readable warnings because Sphinx's + # source root is the original docs/ checkout; those are suppressed in + # conf.template.py (safe: the HTML phase relocates everything so it never + # emits toc.not_readable). needs_inputs = ctx.files.srcs + [config_file] needs_args = [ "--index_file", diff --git a/bazel/rules/rules_score/templates/conf.template.py b/bazel/rules/rules_score/templates/conf.template.py index 0646a342..a158bd4d 100644 --- a/bazel/rules/rules_score/templates/conf.template.py +++ b/bazel/rules/rules_score/templates/conf.template.py @@ -69,6 +69,17 @@ "**/*_design", ] +# Suppress toctree warnings for documents absent from the needs builder's source +# tree. The needs builder runs against only the static docs/ checkout; generated +# files (trlc_rst outputs, renamed_srcs, docs_library_deps) live in bazel-out/ +# and are invisible to it. Their toctree references produce toc.not_readable +# warnings that are cosmetic: the needs builder (sphinx-needs NeedsBuilder) +# captures only `.. need::` directives, not trlc `.. requirement:definition::` +# directives, so needs.json content is unaffected by missing files. +# This suppression is safe for the HTML phase because that phase relocates every +# file into a unified staging directory, so it never encounters toc.not_readable. +suppress_warnings = ["toc.not_readable"] + # Enable markdown rendering source_suffix = { ".rst": "restructuredtext", diff --git a/bazel/rules/rules_score/trlc/config/score_requirements_model.rsl b/bazel/rules/rules_score/trlc/config/score_requirements_model.rsl index 523a3723..d1186214 100644 --- a/bazel/rules/rules_score/trlc/config/score_requirements_model.rsl +++ b/bazel/rules/rules_score/trlc/config/score_requirements_model.rsl @@ -70,9 +70,15 @@ tuple FeatReqId { version Integer } +tuple CompReqSourceId { + item [FeatReq, AssumedSystemReq] + separator @ + version Integer +} + type CompReq "Component-level requirement allocated to a specific software component." extends RequirementSafety { - derived_from "Versioned references to the FeatReq items this component requirement is derived from. Omit only for component-internal requirements with no feature-level parent." - optional FeatReqId[1 .. *] + derived_from "Versioned references to the FeatReq or AssumedSystemReq items this component requirement is derived from. Omit only for component-internal requirements with no feature-level parent." + optional CompReqSourceId[1 .. *] mitigates "Reference to the FailureMode or safety concern that this requirement mitigates." optional String } diff --git a/lobster_bazel/requirements.txt b/lobster_bazel/requirements.txt index 642d64f5..147d7960 100644 --- a/lobster_bazel/requirements.txt +++ b/lobster_bazel/requirements.txt @@ -412,8 +412,8 @@ smmap==5.0.3 \ --hash=sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c \ --hash=sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f # via gitdb -trlc==2.0.3 \ - --hash=sha256:86b4c8690239f7b993259b316b8d6079633e32dff8f8ddbae5ad61503ac9362d +trlc==2.0.5 \ + --hash=sha256:fc4e4e3a21980711e85426d840f4f7f2e6ebb29ce94d55004f7d8e5e4532426c # via bmw-lobster-tool-trlc urllib3==2.7.0 \ --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \