Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
' *******************************************************************************
' Copyright (c) 2026 Contributors to the Eclipse Foundation
'
' See the NOTICE file(s) distributed with this work for additional
' information regarding copyright ownership.
'
' This program and the accompanying materials are made available under the
' terms of the Apache License Version 2.0 which is available at
' https://www.apache.org/licenses/LICENSE-2.0
'
' SPDX-License-Identifier: Apache-2.0
' *******************************************************************************

@startuml safety_analysis_doc_pipeline

' Data flow from safety analysis source files through Bazel rules into the
' Sphinx staging tree.

skinparam linetype ortho
skinparam ArrowFontSize 10
skinparam defaultTextAlignment center
skinparam nodesep 40
skinparam ranksep 50

skinparam rectangle {
BackgroundColor<<src>> #EFF6FB
BorderColor<<src>> #0066B1
BackgroundColor<<gen>> #FFF3E0
BorderColor<<gen>> #EF6C00
BackgroundColor<<rule>> #E8F5E9
BorderColor<<rule>> #2E7D32
BackgroundColor<<stage>> #F3E5F5
BorderColor<<stage>> #7B1FA2
BackgroundColor<<prov>> #FFFDE7
BorderColor<<prov>> #F9A825
}

' ── Inputs ────────────────────────────────────────────────────────────────────
rectangle "failuremodes.trlc\ncontrolmeasures.trlc" <<src>> as trlc
rectangle "fta_*.puml" <<src>> as fta
rectangle "dfa.rst" <<src>> as dfa

' ── fmea rule ─────────────────────────────────────────────────────────────────
rectangle "**fmea**" <<rule>> as fmea

' ── fmea generated files ──────────────────────────────────────────────────────
rectangle "fmea.rst" <<gen>> as fmea_rst
rectangle "failuremodes.inc\ncontrolmeasures.inc" <<gen>> as inc_files
rectangle "fta_*.puml (inlined)\nroot_causes.lobster" <<gen>> as puml_proc
rectangle "detail_*.rst" <<gen>> as detail_rst

' ── fmea SphinxSourcesInfo ────────────────────────────────────────────────────
rectangle "SphinxSourcesInfo\n──────────────────\nsrcs: fmea.rst\ndeps: fmea.rst + *.inc + *.puml\naux_srcs: detail_*.rst" <<prov>> as fmea_ssi

' ── dependability_analysis rule ───────────────────────────────────────────────
rectangle "**dependability_analysis**" <<rule>> as da

' ── dependability_analysis SphinxSourcesInfo ─────────────────────────────────
rectangle "SphinxSourcesInfo\n──────────────────\nsrcs: dfa.rst + fmea.rst\ndeps: + *.inc + *.puml\naux_srcs: detail_*.rst" <<prov>> as da_ssi

' ── dependable_element rule ───────────────────────────────────────────────────
rectangle "**dependable_element**" <<rule>> as de

' ── Sphinx staging tree ───────────────────────────────────────────────────────
rectangle "dependability_analysis/\n dfa.rst ← toctree\n fmea.rst ← toctree\n failuremodes.inc\n controlmeasures.inc\n fta_*.puml\n detail_*.rst ← sub-pages" <<stage>> as stage

' ── Edges ─────────────────────────────────────────────────────────────────────
trlc --> fmea : trlc_rst\nlobster-trlc
fta --> fmea : safety_analysis_tools

fmea --> fmea_rst
fmea --> inc_files
fmea --> puml_proc
fmea --> detail_rst
fmea_rst --> fmea_ssi
inc_files --> fmea_ssi
puml_proc --> fmea_ssi
detail_rst --> fmea_ssi

dfa --> da
fmea_ssi --> da : merge

da --> da_ssi

da_ssi --> de : dependability_analysis attr
de --> stage : symlink srcs+deps\nsymlink aux_srcs\n(no outer toctree entry)

@enduml
29 changes: 29 additions & 0 deletions bazel/rules/rules_score/docs/tooling_architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,32 @@ self-contained.
├── index.html
├── dep1/ ← merged
└── dep2/ ← merged

.. _safety-analysis-doc-pipeline:

Safety analysis document pipeline
----------------------------------

The diagram below shows how FMEA and FTA source files travel through the three
rules (``fmea`` → ``dependability_analysis`` → ``dependable_element``) and land
in the Sphinx staging tree. Blue boxes are source files authored by the
component team; orange boxes are generated files; yellow boxes are the
``SphinxSourcesInfo`` provider payloads; the purple box is the final staging
directory consumed by Sphinx.

.. uml:: _assets/safety_analysis_doc_pipeline.puml
:align: center
:alt: Safety analysis document pipeline
:width: 100%

``SphinxSourcesInfo`` carries three depsets:

- **srcs** — files that become top-level toctree entries in the enclosing
document section (``fmea.rst``, ``dfa.rst``).
- **deps** — all files that must be present in the staging directory: own
``srcs`` plus ``.inc`` rendered sections and preprocessed ``.puml`` diagrams
that ``fmea.rst`` pulls in via ``.. include::`` / ``.. uml::``.
- **aux_srcs** — files to symlink alongside ``srcs``/``deps`` but **not** added
to the outer index toctree. ``fmea`` uses this for the ``detail_*.rst``
sub-pages, which are referenced from the inner ``.. toctree::`` inside
``fmea.rst`` rather than from the section index.
1 change: 1 addition & 0 deletions bazel/rules/rules_score/private/architectural_design.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def _architectural_design_impl(ctx):
SphinxSourcesInfo(
srcs = sphinx_srcs,
deps = sphinx_srcs,
aux_srcs = depset(),
),
]

Expand Down
1 change: 1 addition & 0 deletions bazel/rules/rules_score/private/assumptions_of_use.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def _assumptions_of_use_impl(ctx):
SphinxSourcesInfo(
srcs = all_srcs,
deps = depset(transitive = transitive),
aux_srcs = depset(),
),
]

Expand Down
1 change: 1 addition & 0 deletions bazel/rules/rules_score/private/component.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ def _component_impl(ctx):
SphinxSourcesInfo(
srcs = req_sphinx_depset,
deps = sphinx_depset,
aux_srcs = depset(),
),
]

Expand Down
18 changes: 12 additions & 6 deletions bazel/rules/rules_score/private/dependability_analysis.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ load("//bazel/rules/rules_score/private:lobster_config.bzl", "format_lobster_sou
# Private Helpers
# ============================================================================

def _collect_analysis_providers(sa, rst_srcs_list, rst_deps_list, lobster_files):
def _collect_analysis_providers(sa, rst_srcs_list, rst_deps_list, rst_aux_list, lobster_files):
"""Collect analysis providers from a single sub-analysis target.

Updates the provided lists/dicts in-place.
Expand All @@ -39,12 +39,15 @@ def _collect_analysis_providers(sa, rst_srcs_list, rst_deps_list, lobster_files)
sa: A sub-analysis target (fmea or security).
rst_srcs_list: List of depsets to extend with SphinxSourcesInfo.srcs.
rst_deps_list: List of depsets to extend with SphinxSourcesInfo.deps.
rst_aux_list: List of depsets to extend with SphinxSourcesInfo.aux_srcs.
lobster_files: Dict to update with AnalysisInfo.lobster_files
(canonical name → File).
"""
if SphinxSourcesInfo in sa:
rst_srcs_list.append(sa[SphinxSourcesInfo].srcs)
rst_deps_list.append(sa[SphinxSourcesInfo].deps)
if sa[SphinxSourcesInfo].aux_srcs:
rst_aux_list.append(sa[SphinxSourcesInfo].aux_srcs)
if AnalysisInfo in sa:
lobster_files.update(sa[AnalysisInfo].lobster_files)

Expand Down Expand Up @@ -74,6 +77,7 @@ def _dependability_analysis_impl(ctx):

rst_srcs_transitive = [dfa_rst_files]
rst_deps_transitive = [dfa_rst_files]
rst_aux_transitive = []
lobster_files = {} # canonical name → File, merged from all sub-analyses

# -------------------------------------------------------------------------
Expand All @@ -82,22 +86,23 @@ def _dependability_analysis_impl(ctx):
fmea_output_files = []
for sa in ctx.attr.fmea:
fmea_output_files.append(sa[DefaultInfo].files)
_collect_analysis_providers(sa, rst_srcs_transitive, rst_deps_transitive, lobster_files)
_collect_analysis_providers(sa, rst_srcs_transitive, rst_deps_transitive, rst_aux_transitive, lobster_files)

# -------------------------------------------------------------------------
# Collect from security_analysis targets
# -------------------------------------------------------------------------
security_output_files = []
for sa in ctx.attr.security_analysis:
security_output_files.append(sa[DefaultInfo].files)
_collect_analysis_providers(sa, rst_srcs_transitive, rst_deps_transitive, lobster_files)
_collect_analysis_providers(sa, rst_srcs_transitive, rst_deps_transitive, rst_aux_transitive, lobster_files)

# Architectural design sphinx deps (optional)
if ctx.attr.arch_design and SphinxSourcesInfo in ctx.attr.arch_design:
rst_deps_transitive.append(ctx.attr.arch_design[SphinxSourcesInfo].deps)
# arch_design files are handled separately by dependable_element via its
# architectural_design attribute, so they are not included in this rule's
# sphinx deps to avoid orphan warnings.

all_rst_srcs = depset(transitive = rst_srcs_transitive)
all_rst_deps = depset(transitive = rst_deps_transitive)
all_rst_aux = depset(transitive = rst_aux_transitive) if rst_aux_transitive else depset()

# =========================================================================
# Lobster traceability report (combined FM + CM + FTA)
Expand Down Expand Up @@ -182,6 +187,7 @@ def _dependability_analysis_impl(ctx):
SphinxSourcesInfo(
srcs = all_rst_srcs,
deps = all_rst_deps,
aux_srcs = all_rst_aux,
),
]

Expand Down
24 changes: 20 additions & 4 deletions bazel/rules/rules_score/private/dependable_element.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -254,16 +254,21 @@ def _process_artifact_files(ctx, artifact_name, label):
all_files = _get_sphinx_files(label)
doc_files = _filter_doc_files(all_files)

if not doc_files:
# aux_srcs are files to symlink but NOT add to the outer toctree index.
aux_files = []
if label[SphinxSourcesInfo].aux_srcs:
aux_files = label[SphinxSourcesInfo].aux_srcs.to_list()

if not doc_files and not aux_files:
return (output_files, index_refs)

# Build a lookup of srcs paths so we know which files are toctree entries.
srcs_paths = {f.path: True for f in label[SphinxSourcesInfo].srcs.to_list()}

# Find common directory to preserve hierarchy
common_dir = _find_common_directory(doc_files)
# Find common directory across all files (regular + aux) to preserve hierarchy.
common_dir = _find_common_directory(doc_files + aux_files)

# Process each file
# Process regular deps files
for artifact_file in doc_files:
# Compute paths
relative_path = _compute_relative_path(artifact_file, common_dir)
Expand All @@ -289,6 +294,17 @@ def _process_artifact_files(ctx, artifact_name, label):
.replace(".md", "")
index_refs.append(doc_ref)

# Process aux_srcs: symlink without adding to outer toctree index.
for artifact_file in aux_files:
relative_path = _compute_relative_path(artifact_file, common_dir)
output_file = _create_artifact_symlink(
ctx,
artifact_name,
artifact_file,
relative_path,
)
output_files.append(output_file)

return (output_files, index_refs)

def _process_artifact_type(ctx, artifact_name):
Expand Down
15 changes: 10 additions & 5 deletions bazel/rules/rules_score/private/fmea.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -282,14 +282,18 @@ def _fmea_impl(ctx):
lobster_files["root_causes.lobster"] = f

# detail_rsts are NOT top-level toctree entries (they live in sub-toctrees
# within fmea.rst), but they must be present in the Sphinx tree. They go
# into deps so dependable_element symlinks them alongside toctree files.
# within fmea.rst), but they must be symlinked alongside fmea.rst so Sphinx
# can resolve the toctree references. They go into aux_srcs so that
# dependable_element symlinks them without adding them to the outer index.
toctree_files = [f for f in output_files if f not in detail_rsts]
all_sphinx_srcs = depset(toctree_files)

sphinx_deps = [all_sphinx_srcs, depset(detail_rsts)]
if ctx.attr.arch_design and SphinxSourcesInfo in ctx.attr.arch_design:
sphinx_deps.append(ctx.attr.arch_design[SphinxSourcesInfo].deps)
# Only include fmea's own generated files in the sphinx deps. arch_design
# files are handled separately by dependable_element via its
# architectural_design attribute, so omitting them here avoids their RST
# wrappers being symlinked into the dependability_analysis/ section as
# orphaned documents.
sphinx_deps = [all_sphinx_srcs]

return [
DefaultInfo(
Expand All @@ -302,6 +306,7 @@ def _fmea_impl(ctx):
SphinxSourcesInfo(
srcs = all_sphinx_srcs,
deps = depset(transitive = sphinx_deps),
aux_srcs = depset(detail_rsts),
),
]

Expand Down
1 change: 1 addition & 0 deletions bazel/rules/rules_score/private/requirements.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def _requirements_impl(ctx):
SphinxSourcesInfo(
srcs = sphinx_srcs,
deps = depset(transitive = transitive_sphinx),
aux_srcs = depset(),
),
]

Expand Down
1 change: 1 addition & 0 deletions bazel/rules/rules_score/private/sphinx_module.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def _score_html_impl(ctx):
"json_path": dep[SphinxNeedsInfo].needs_json_file.path, # Use direct file
"id_prefix": "",
"css_class": "",
"version": "1.0",
}
for dep in ctx.attr.deps:
if SphinxModuleInfo in dep:
Expand Down
1 change: 1 addition & 0 deletions bazel/rules/rules_score/private/unit.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def _unit_impl(ctx):
SphinxSourcesInfo(
srcs = all_files,
deps = depset(transitive = [all_files] + sphinx_design_deps),
aux_srcs = depset(),
),
]

Expand Down
1 change: 1 addition & 0 deletions bazel/rules/rules_score/private/unit_design.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def _unit_design_impl(ctx):
SphinxSourcesInfo(
srcs = all_source_files,
deps = all_source_files,
aux_srcs = depset(),
),
]

Expand Down
21 changes: 14 additions & 7 deletions bazel/rules/rules_score/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,24 @@ SphinxSourcesInfo = provider(
this to enable integration with sphinx_module and dependable_element.

Semantics:
srcs — Files directly owned/generated by this rule. Only these are
used as top-level toctree entries by dependable_element.
For leaf rules (no children), srcs == deps.
deps — All files needed in the Sphinx tree: own srcs plus all
transitive files from children. dependable_element symlinks
everything in deps into the output tree. Parent/container
rules bubble up children's deps into their own deps field.
srcs — Files directly owned/generated by this rule. Only these are
used as top-level toctree entries by dependable_element.
For leaf rules (no children), srcs == deps.
deps — All files needed in the Sphinx tree: own srcs plus all
transitive files from children. dependable_element symlinks
everything in deps into the output tree. Parent/container
rules bubble up children's deps into their own deps field.
aux_srcs — Auxiliary files that must be present in the Sphinx source
tree but must NOT appear as top-level toctree entries.
Typically detail pages (e.g. detail_*_fta.rst) that are
referenced from within another page's own toctree directive.
dependable_element symlinks these alongside srcs/deps but
does not add them to the outer index toctree.
""",
fields = {
"srcs": "Depset of files directly owned by this rule (.rst, .md, .puml, etc.). Used by dependable_element as toctree entries.",
"deps": "Depset of all files needed in the Sphinx tree — own srcs plus transitive deps from all children. For leaf rules, equals srcs.",
"aux_srcs": "Depset of files to symlink into the Sphinx tree but NOT add to the outer toctree (e.g. detail sub-pages referenced from within another page).",
},
)

Expand Down
Loading