diff --git a/bazel/rules/rules_score/BUILD b/bazel/rules/rules_score/BUILD index 3eea67be..4a50d759 100644 --- a/bazel/rules/rules_score/BUILD +++ b/bazel/rules/rules_score/BUILD @@ -29,6 +29,7 @@ exports_files([ "templates/unit.template.rst", "templates/component.template.rst", "templates/fmea.template.rst", + "templates/puml_diagram.template.rst", ]) compile_pip_requirements( diff --git a/bazel/rules/rules_score/private/architectural_design.bzl b/bazel/rules/rules_score/private/architectural_design.bzl index f2b85bde..75682a0f 100644 --- a/bazel/rules/rules_score/private/architectural_design.bzl +++ b/bazel/rules/rules_score/private/architectural_design.bzl @@ -23,6 +23,7 @@ to produce FlatBuffers binary representations of the parsed diagrams. """ load("//bazel/rules/rules_score:providers.bzl", "ArchitecturalDesignInfo", "SphinxSourcesInfo") +load("//bazel/rules/rules_score/private:puml_utils.bzl", "make_puml_rst_wrappers") load("//bazel/rules/rules_score/private:verbosity.bzl", "VERBOSITY_ATTR", "get_log_level") # ============================================================================ @@ -149,6 +150,17 @@ def _architectural_design_impl(ctx): transitive = [all_source_files], ) + # Generate a thin RST wrapper for every .puml diagram so it appears as a + # toctree entry in the dependable_element index. + rst_wrappers = make_puml_rst_wrappers( + ctx, + ctx.files.static + ctx.files.dynamic, + ctx.label.name, + ctx.file._puml_rst_template, + ) + + sphinx_srcs = depset(rst_wrappers, transitive = [sphinx_files]) + return [ DefaultInfo(files = all_source_files), ArchitecturalDesignInfo( @@ -160,8 +172,9 @@ def _architectural_design_impl(ctx): ), # Source diagram files + plantuml_links.json for the sphinx documentation build SphinxSourcesInfo( - srcs = sphinx_files, - deps = sphinx_files, + srcs = sphinx_srcs, + deps = sphinx_srcs, + ancillary = depset(), ), ] @@ -205,6 +218,11 @@ _architectural_design = rule( cfg = "exec", doc = "Tool that generates plantuml_links.json from FlatBuffers diagram outputs", ), + "_puml_rst_template": attr.label( + default = Label("//bazel/rules/rules_score:templates/puml_diagram.template.rst"), + allow_single_file = True, + doc = "RST template for PlantUML diagram wrapper pages.", + ), }, **VERBOSITY_ATTR ), diff --git a/bazel/rules/rules_score/private/assumptions_of_use.bzl b/bazel/rules/rules_score/private/assumptions_of_use.bzl index ee429707..02012a9d 100644 --- a/bazel/rules/rules_score/private/assumptions_of_use.bzl +++ b/bazel/rules/rules_score/private/assumptions_of_use.bzl @@ -39,7 +39,27 @@ def _assumptions_of_use_impl(ctx): Returns: List of providers including DefaultInfo and AssumptionsOfUseInfo """ - srcs = depset(ctx.files.srcs) + + # Render each TRLC source to RST for Sphinx + rendered_files = [] + for src in ctx.attr.srcs: + trlc_provider = src[TrlcProviderInfo] + rendered_file = ctx.actions.declare_file("{}_{}.rst".format(ctx.attr.name, src.label.name)) + args = ctx.actions.args() + args.add("--output", rendered_file.path) + args.add("--input-dir", ".") + args.add("--title", ctx.label.name.replace("_", " ").title()) + args.add("--source-files") + args.add_all(trlc_provider.reqs) + ctx.actions.run( + inputs = src[DefaultInfo].files, + outputs = [rendered_file], + arguments = [args], + executable = ctx.executable._renderer, + ) + rendered_files.append(rendered_file) + + all_srcs = depset(rendered_files) # Collect requirements providers and lobster files reqs = [] @@ -55,20 +75,21 @@ def _assumptions_of_use_impl(ctx): lobster_files.append(info.srcs) # Collect transitive sphinx sources from requirements - transitive = [srcs] + transitive = [all_srcs] for req in ctx.attr.requirements: if SphinxSourcesInfo in req: transitive.append(req[SphinxSourcesInfo].deps) return [ - DefaultInfo(files = srcs), + DefaultInfo(files = all_srcs), AssumptionsOfUseInfo( srcs = depset(transitive = lobster_files), name = ctx.label.name, ), SphinxSourcesInfo( - srcs = srcs, + srcs = all_srcs, deps = depset(transitive = transitive), + ancillary = depset(), ), ] @@ -90,6 +111,12 @@ _assumptions_of_use = rule( mandatory = False, doc = "List of feature or component requirements targets that these Assumptions of Use trace to", ), + "_renderer": attr.label( + default = Label("@trlc//tools/trlc_rst:trlc_rst"), + executable = True, + allow_files = True, + cfg = "exec", + ), }, ) diff --git a/bazel/rules/rules_score/private/component.bzl b/bazel/rules/rules_score/private/component.bzl index bd070b6e..60a9f109 100644 --- a/bazel/rules/rules_score/private/component.bzl +++ b/bazel/rules/rules_score/private/component.bzl @@ -207,6 +207,7 @@ def _component_impl(ctx): SphinxSourcesInfo( srcs = req_sphinx_depset, deps = sphinx_depset, + ancillary = depset(), ), ] diff --git a/bazel/rules/rules_score/private/component_requirements.bzl b/bazel/rules/rules_score/private/component_requirements.bzl index ad6fc4ac..e0fddfed 100644 --- a/bazel/rules/rules_score/private/component_requirements.bzl +++ b/bazel/rules/rules_score/private/component_requirements.bzl @@ -55,7 +55,8 @@ def _component_requirements_impl(ctx): ), SphinxSourcesInfo( srcs = srcs, - transitive_srcs = depset(transitive = transitive), + deps = depset(transitive = transitive), + ancillary = depset(), ), ] diff --git a/bazel/rules/rules_score/private/dependability_analysis.bzl b/bazel/rules/rules_score/private/dependability_analysis.bzl index 233caa0b..68637d71 100644 --- a/bazel/rules/rules_score/private/dependability_analysis.bzl +++ b/bazel/rules/rules_score/private/dependability_analysis.bzl @@ -30,21 +30,23 @@ 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_ancillary_list, lobster_files): """Collect analysis providers from a single sub-analysis target. Updates the provided lists/dicts in-place. Args: - 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. - lobster_files: Dict to update with AnalysisInfo.lobster_files - (canonical name → File). + 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_ancillary_list: List of depsets to extend with SphinxSourcesInfo.ancillary. + 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) + rst_ancillary_list.append(sa[SphinxSourcesInfo].ancillary) if AnalysisInfo in sa: lobster_files.update(sa[AnalysisInfo].lobster_files) @@ -74,6 +76,7 @@ def _dependability_analysis_impl(ctx): rst_srcs_transitive = [dfa_rst_files] rst_deps_transitive = [dfa_rst_files] + rst_ancillary_transitive = [] lobster_files = {} # canonical name → File, merged from all sub-analyses # ------------------------------------------------------------------------- @@ -82,7 +85,7 @@ 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_ancillary_transitive, lobster_files) # ------------------------------------------------------------------------- # Collect from security_analysis targets @@ -90,7 +93,7 @@ def _dependability_analysis_impl(ctx): 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_ancillary_transitive, lobster_files) # Architectural design sphinx deps (optional) if ctx.attr.arch_design and SphinxSourcesInfo in ctx.attr.arch_design: @@ -182,6 +185,7 @@ def _dependability_analysis_impl(ctx): SphinxSourcesInfo( srcs = all_rst_srcs, deps = all_rst_deps, + ancillary = depset(transitive = rst_ancillary_transitive), ), ] diff --git a/bazel/rules/rules_score/private/dependable_element.bzl b/bazel/rules/rules_score/private/dependable_element.bzl index c1b68310..d97c124b 100644 --- a/bazel/rules/rules_score/private/dependable_element.bzl +++ b/bazel/rules/rules_score/private/dependable_element.bzl @@ -280,6 +280,21 @@ def _process_artifact_files(ctx, artifact_name, label): .replace(".md", "") index_refs.append(doc_ref) + # Symlink ancillary files (present for sub-toctrees / .. uml:: resolution, + # but NOT added to the outer toctree index). + if SphinxSourcesInfo in label: + for anc_file in label[SphinxSourcesInfo].ancillary.to_list(): + if anc_file.extension not in ["rst", "md", "puml", "plantuml", "png", "svg", "inc", "json"]: + continue + relative_path = _compute_relative_path(anc_file, _find_common_directory([anc_file])) + output_file = _create_artifact_symlink( + ctx, + artifact_name, + anc_file, + relative_path, + ) + output_files.append(output_file) + return (output_files, index_refs) def _process_artifact_type(ctx, artifact_name): @@ -686,10 +701,8 @@ def _dependable_element_index_impl(ctx): # Process each well-known artifact type into symlinked output files and # toctree references for the index template. - # "requirements" covers both feature_requirements and component_requirements. artifact_types = [ "assumptions_of_use", - "requirements", "architectural_design", "dependability_analysis", "checklists", @@ -701,6 +714,24 @@ def _dependable_element_index_impl(ctx): output_files.extend(files) artifacts_by_type[artifact_name] = refs + # Collect feature_requirements refs from requirements targets that + # carry FeatureRequirementsInfo. + feature_req_refs = [] + for req_target in ctx.attr.requirements: + if FeatureRequirementsInfo in req_target: + label_files, label_refs = _process_artifact_files(ctx, "feature_requirements", req_target) + output_files.extend(label_files) + feature_req_refs.extend(label_refs) + + # Collect assumed_system_requirements refs from requirements targets that + # carry AssumedSystemRequirementsInfo. + assumed_system_req_refs = [] + for req_target in ctx.attr.requirements: + if AssumedSystemRequirementsInfo in req_target: + label_files, label_refs = _process_artifact_files(ctx, "assumed_system_requirements", req_target) + output_files.extend(label_files) + assumed_system_req_refs.extend(label_refs) + # Collect all units recursively from components all_units = _collect_units_recursive(ctx.attr.components) @@ -746,18 +777,11 @@ def _dependable_element_index_impl(ctx): if CertifiedScope in dep: collected_certified_scopes.append(dep[CertifiedScope].transitive_scopes) - # Generate an intermediate components/index.rst that groups all component pages - # under a single "Components" navigation entry in the Sphinx sidebar. + # Reference component pages directly in the outer toctree, avoiding an + # intermediate components/index.rst that would repeat "Components" in the + # Sphinx sidebar navigation. if component_refs: - comp_index_rst = ctx.actions.declare_file(ctx.label.name + "/components/index.rst") - comp_index_underline = "=" * len("Components") - comp_toctree_entries = "\n ".join(component_refs) - ctx.actions.write( - output = comp_index_rst, - content = "Components\n" + comp_index_underline + "\n\n.. toctree::\n :maxdepth: 2\n\n " + comp_toctree_entries + "\n", - ) - output_files.append(comp_index_rst) - components_ref = "components/index" + components_ref = "\n ".join(["components/" + name for name in component_refs]) else: components_ref = "" @@ -775,8 +799,9 @@ def _dependable_element_index_impl(ctx): "{title}": title, "{underline}": underline, "{components}": components_ref, + "{assumed_system_requirements}": "\n ".join(assumed_system_req_refs), "{assumptions_of_use}": "\n ".join(artifacts_by_type["assumptions_of_use"]), - "{requirements}": "\n ".join(artifacts_by_type["requirements"]), + "{feature_requirements}": "\n ".join(feature_req_refs), "{architectural_design}": "\n ".join(artifacts_by_type["architectural_design"]), "{dependability_analysis}": "\n ".join(artifacts_by_type["dependability_analysis"]), "{checklists}": "\n ".join(artifacts_by_type["checklists"]), @@ -1000,8 +1025,8 @@ _dependable_element_index = rule( ), "requirements": attr.label_list( mandatory = True, - providers = [FeatureRequirementsInfo], - doc = "Feature requirements targets (feature_requirements only).", + providers = [[FeatureRequirementsInfo], [AssumedSystemRequirementsInfo]], + doc = "Feature or assumed system requirements targets.", ), "architectural_design": attr.label_list( mandatory = True, diff --git a/bazel/rules/rules_score/private/feature_requirements.bzl b/bazel/rules/rules_score/private/feature_requirements.bzl index 2f04d66e..95cabb90 100644 --- a/bazel/rules/rules_score/private/feature_requirements.bzl +++ b/bazel/rules/rules_score/private/feature_requirements.bzl @@ -49,7 +49,8 @@ def _feature_requirements_impl(ctx): ), SphinxSourcesInfo( srcs = srcs, - transitive_srcs = srcs, + deps = srcs, + ancillary = depset(), ), ] diff --git a/bazel/rules/rules_score/private/fmea.bzl b/bazel/rules/rules_score/private/fmea.bzl index 463e3f89..f59b419d 100644 --- a/bazel/rules/rules_score/private/fmea.bzl +++ b/bazel/rules/rules_score/private/fmea.bzl @@ -43,6 +43,7 @@ by the ``dependability_analysis`` rule which wraps this one. load("@trlc//:trlc.bzl", "TrlcProviderInfo") load("//bazel/rules/rules_score:providers.bzl", "AnalysisInfo", "ArchitecturalDesignInfo", "SphinxSourcesInfo") +load("//bazel/rules/rules_score/private:puml_utils.bzl", "make_puml_rst_wrappers") load("//bazel/rules/rules_score/private:verbosity.bzl", "VERBOSITY_ATTR", "get_log_level") # ============================================================================ @@ -58,7 +59,7 @@ def _process_root_causes(ctx): ``ctx.executable._safety_analysis_tools``. Returns: - Tuple ``(preprocessed_diagrams, [root_causes_lobster], rst_section_text)``. + Tuple ``(preprocessed_diagrams, detail_rsts, [root_causes_lobster], rst_section_text)``. All lists are empty and the section text is ``""`` when there are no PlantUML root-cause inputs. """ @@ -68,7 +69,7 @@ def _process_root_causes(ctx): if f.extension in ("puml", "plantuml") ] if not puml_inputs: - return [], [], "" + return [], [], [], "" # Declare one preprocessed output per input diagram (same directory). preprocessed_diagrams = [ @@ -95,13 +96,34 @@ def _process_root_causes(ctx): progress_message = "Processing root cause FTA diagrams for %s" % ctx.label.name, ) - # Build the RST fragment that fmea.rst embeds. - diagrams_rst = "\n\n".join( - [".. uml:: " + diagram.basename for diagram in preprocessed_diagrams], + # Generate one detail RST per preprocessed FTA diagram via the shared + # puml_diagram template. The "fta_" prefix is stripped from the stem so + # the page is titled e.g. "Server Not Listening" instead of + # "Fta Server Not Listening". + detail_rsts = make_puml_rst_wrappers( + ctx, + preprocessed_diagrams, + ctx.label.name, + ctx.file._puml_rst_template, + strip_prefix = "fta_", + filename_prefix = "detail_", ) - root_causes_rst_section = "Root Causes\n-----------\n\n{}".format(diagrams_rst) - return preprocessed_diagrams, [root_causes_lobster], root_causes_rst_section + # Build toctree entries directly from the declared RST wrapper filenames so + # the toctree is always consistent with what make_puml_rst_wrappers produces, + # regardless of any prefix convention on the input files. + toctree_entries = [ + " " + rst.basename[:-4] # strip ".rst" + for rst in detail_rsts + ] + + root_causes_rst_section = ( + "Root Cause Analysis\n-------------------\n\n" + + ".. toctree::\n :maxdepth: 1\n\n" + + "\n".join(toctree_entries) + "\n" + ) + + return preprocessed_diagrams, detail_rsts, [root_causes_lobster], root_causes_rst_section # ============================================================================ # Private Helpers @@ -150,8 +172,9 @@ def _fmea_impl(ctx): # ------------------------------------------------------------------------- # 0. Process root causes (FTA diagrams) if provided # ------------------------------------------------------------------------- - preprocessed_diagrams, root_cause_lobster_files, root_causes_rst_section = _process_root_causes(ctx) + preprocessed_diagrams, detail_rsts, root_cause_lobster_files, root_causes_rst_section = _process_root_causes(ctx) output_files.extend(preprocessed_diagrams) + output_files.extend(detail_rsts) # ------------------------------------------------------------------------- # 1. Render failure modes: TRLC -> .inc via trlc_rst @@ -272,7 +295,10 @@ def _fmea_impl(ctx): for f in root_cause_lobster_files: lobster_files["root_causes.lobster"] = f - all_sphinx_srcs = depset(output_files) + # detail_rsts are ancillary: they must be present next to fmea.rst for the + # sub-toctree to resolve, but they are NOT top-level toctree entries. + 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] if ctx.attr.arch_design and SphinxSourcesInfo in ctx.attr.arch_design: @@ -289,6 +315,7 @@ def _fmea_impl(ctx): SphinxSourcesInfo( srcs = all_sphinx_srcs, deps = depset(transitive = sphinx_deps), + ancillary = depset(detail_rsts), ), ] @@ -364,6 +391,11 @@ _fmea = rule( allow_single_file = True, doc = "RST template for the FMEA page.", ), + "_puml_rst_template": attr.label( + default = Label("//bazel/rules/rules_score:templates/puml_diagram.template.rst"), + allow_single_file = True, + doc = "RST template for PlantUML diagram wrapper pages.", + ), }, **VERBOSITY_ATTR ), diff --git a/bazel/rules/rules_score/private/puml_utils.bzl b/bazel/rules/rules_score/private/puml_utils.bzl new file mode 100644 index 00000000..0a271e4a --- /dev/null +++ b/bazel/rules/rules_score/private/puml_utils.bzl @@ -0,0 +1,59 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +"""Shared helper for generating RST wrapper pages for PlantUML diagram files.""" + +def make_puml_rst_wrappers(ctx, puml_files, output_dir, template, strip_prefix = "", filename_prefix = ""): + """Generate a thin RST wrapper page for each PlantUML diagram file. + + The wrapper embeds the diagram via ``.. uml::`` so it appears as a + proper toctree entry while keeping the source ``.puml`` file separate. + + Args: + ctx: Rule context. + puml_files: Iterable of File objects whose extension is ``puml`` or + ``plantuml``. + output_dir: String prefix for declared output files + (e.g. ``ctx.label.name``). + template: The ``puml_diagram.template.rst`` File (from + ``ctx.file._puml_rst_template``). + strip_prefix: Optional filename stem prefix to strip before deriving + the human-readable title (e.g. ``"fta_"``). + filename_prefix: Optional prefix prepended to the output RST filename + stem (e.g. ``"detail_"``). + + Returns: + List of declared ``.rst`` output Files, one per input diagram. + """ + wrappers = [] + for f in puml_files: + if f.extension not in ("puml", "plantuml"): + continue + stem = f.basename[:-(len(f.extension) + 1)] + if strip_prefix and stem.startswith(strip_prefix): + stem = stem[len(strip_prefix):] + title = stem.replace("_", " ").title() + wrapper = ctx.actions.declare_file( + "{}/{}{}.rst".format(output_dir, filename_prefix, stem), + ) + ctx.actions.expand_template( + template = template, + output = wrapper, + substitutions = { + "{title}": title, + "{underline}": "=" * len(title), + "{basename}": f.basename, + }, + ) + wrappers.append(wrapper) + return wrappers diff --git a/bazel/rules/rules_score/private/requirements.bzl b/bazel/rules/rules_score/private/requirements.bzl index 18263a94..f7889897 100644 --- a/bazel/rules/rules_score/private/requirements.bzl +++ b/bazel/rules/rules_score/private/requirements.bzl @@ -49,6 +49,7 @@ def _requirements_impl(ctx): args = ctx.actions.args() args.add("--output", rendered_file.path) args.add("--input-dir", ".") + args.add("--title", ctx.label.name.replace("_", " ").title()) args.add("--source-files") args.add_all(trlc_provider.reqs) @@ -113,6 +114,7 @@ def _requirements_impl(ctx): providers.append(SphinxSourcesInfo( srcs = all_srcs, deps = all_srcs, + ancillary = depset(), )) return providers diff --git a/bazel/rules/rules_score/private/unit.bzl b/bazel/rules/rules_score/private/unit.bzl index c03e2b9d..ee0517cd 100644 --- a/bazel/rules/rules_score/private/unit.bzl +++ b/bazel/rules/rules_score/private/unit.bzl @@ -96,6 +96,7 @@ def _unit_impl(ctx): SphinxSourcesInfo( srcs = all_files, deps = depset(transitive = [all_files] + sphinx_design_deps), + ancillary = depset(), ), ] diff --git a/bazel/rules/rules_score/private/unit_design.bzl b/bazel/rules/rules_score/private/unit_design.bzl index 1b0e4bc6..266e4362 100644 --- a/bazel/rules/rules_score/private/unit_design.bzl +++ b/bazel/rules/rules_score/private/unit_design.bzl @@ -95,6 +95,7 @@ def _unit_design_impl(ctx): SphinxSourcesInfo( srcs = all_source_files, deps = all_source_files, + ancillary = depset(), ), ] diff --git a/bazel/rules/rules_score/providers.bzl b/bazel/rules/rules_score/providers.bzl index c564c854..44ef0590 100644 --- a/bazel/rules/rules_score/providers.bzl +++ b/bazel/rules/rules_score/providers.bzl @@ -53,6 +53,7 @@ SphinxSourcesInfo = provider( fields = { "srcs": "Depset of direct source files for Sphinx documentation (.rst, .md, .puml, .plantuml, .svg, .png, etc.)", "deps": "Depset of transitive Sphinx source files collected from all direct and transitive dependencies.", + "ancillary": "Depset of files that must be physically present in the Sphinx tree (e.g. for sub-toctrees or .. uml:: directives) but are NOT top-level toctree entries.", }, ) diff --git a/bazel/rules/rules_score/templates/dependable_element_index.template.rst b/bazel/rules/rules_score/templates/dependable_element_index.template.rst index 0186adce..a6cc37c7 100644 --- a/bazel/rules/rules_score/templates/dependable_element_index.template.rst +++ b/bazel/rules/rules_score/templates/dependable_element_index.template.rst @@ -15,30 +15,25 @@ Dependable element: {title} ===================={underline} -Architectural Design --------------------- - -.. toctree:: - :maxdepth: 2 - - {architectural_design} - - -Assumptions of Use ------------------- +Assumed System +-------------- .. toctree:: :maxdepth: 2 + {assumed_system_requirements} {assumptions_of_use} -Requirements ------------- +Software Architectural Level +---------------------------- .. toctree:: :maxdepth: 2 - {requirements} + {feature_requirements} + {architectural_design} + {dependability_analysis} + Components ---------- @@ -49,16 +44,6 @@ Components {components} - - -Dependability Analysis ----------------------- - -.. toctree:: - :maxdepth: 2 - - {dependability_analysis} - Checklists ---------- diff --git a/bazel/rules/rules_score/templates/fmea.template.rst b/bazel/rules/rules_score/templates/fmea.template.rst index 5d51ef8f..4afca3dd 100644 --- a/bazel/rules/rules_score/templates/fmea.template.rst +++ b/bazel/rules/rules_score/templates/fmea.template.rst @@ -17,6 +17,6 @@ {failure_modes_section} -{control_measures_section} - {root_causes_section} + +{control_measures_section} diff --git a/bazel/rules/rules_score/templates/puml_diagram.template.rst b/bazel/rules/rules_score/templates/puml_diagram.template.rst new file mode 100644 index 00000000..6edbddbb --- /dev/null +++ b/bazel/rules/rules_score/templates/puml_diagram.template.rst @@ -0,0 +1,18 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +{title} +{underline} + +.. uml:: {basename} diff --git a/bazel/rules/rules_score/test/seooc_test.bzl b/bazel/rules/rules_score/test/seooc_test.bzl index bbd2e4e7..3d8d0a90 100644 --- a/bazel/rules/rules_score/test/seooc_test.bzl +++ b/bazel/rules/rules_score/test/seooc_test.bzl @@ -119,46 +119,51 @@ seooc_needs_provider_test = analysistest.make( # ============================================================================ # Regression tests: correct index.rst resolution (multi-index scenario) # -# These tests guard against a bug where _score_html_impl scanned all relocated -# source files for any path ending with "/index.rst" and used the last match. -# A dependable_element with components generates both: -# /index.rst (root – correct Sphinx entry point) -# /components/index.rst (sub-page – must NOT be the entry point) -# The old code would pick the sub-page as the Sphinx entry point. +# These tests guard against a bug where the wrong index.rst was picked as the +# Sphinx entry point when multiple index.rst files are present in the output +# tree (e.g. unit/index.rst, component/index.rst alongside the root index.rst). +# The fix is to carry the correct File explicitly via SphinxIndexFileInfo rather +# than scanning all output files for any path ending with "/index.rst". # ============================================================================ def _seooc_multi_index_files_exist_test_impl(ctx): """ - Given a dependable_element with at least one component, - When the _index rule generates its output file set, - Then both a root index.rst and a components/index.rst must be present, - confirming the fixture exercises the multi-index scenario. + Given a dependable_element with at least one component (which generates + multiple .rst files in subdirectories alongside the root index.rst), + When the _index rule finishes, + Then SphinxIndexFileInfo must be provided and its index_file must be the + root index.rst (not inside any subdirectory), confirming the correct + entry point is propagated explicitly rather than discovered by scanning. """ env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) - # When: collect all output paths - files = target_under_test[DefaultInfo].files.to_list() - paths = [f.path for f in files] + # Then: SphinxIndexFileInfo provider must be present + asserts.true( + env, + SphinxIndexFileInfo in target_under_test, + "Expected SphinxIndexFileInfo provider to be present on the index target", + ) - # Then: a components/index.rst sub-page exists - components_index_paths = [p for p in paths if p.endswith("components/index.rst")] + index_file = target_under_test[SphinxIndexFileInfo].index_file + + # Then: the index file must be named index.rst asserts.true( env, - len(components_index_paths) >= 1, - "Expected a components/index.rst to be generated (fixture must have at least one component)", + index_file.basename == "index.rst", + "SphinxIndexFileInfo.index_file must be named index.rst, got: " + index_file.basename, ) - # Then: a root index.rst (not inside components/) also exists - root_index_paths = [ - p - for p in paths - if p.endswith("index.rst") and "components/" not in p - ] + # Then: the index file must be at the root of the output tree (no subdirectory in dirname) + # e.g. .../test_dependable_element_index/index.rst — the last path component of dirname + # must equal the target name, not a sub-page like "components" or "units". + parent = index_file.dirname.split("/")[-1] asserts.true( env, - len(root_index_paths) >= 1, - "Expected a root index.rst (outside components/) to be generated", + parent == ctx.attr.target_under_test.label.name, + "SphinxIndexFileInfo.index_file must sit directly under the target output dir, " + + "not in a subdirectory. dirname ends in '" + parent + "', expected '" + + ctx.attr.target_under_test.label.name + "'", ) return analysistest.end(env)