From 64e0cb7ef5830b4ab2cfa039c1355bdf98098330 Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Wed, 17 Dec 2025 11:42:58 +0100 Subject: [PATCH 1/9] [module api] Initial version This commit introduces the initial implementation of the score_module Bazel rules for handling SEOOC (Safety Element Out Of Context) modules. Key additions: - Core rules in score_module.bzl and private/seooc.bzl - Test infrastructure with fixtures covering architecture design, assumptions of use, component requirements, and safety analysis --- bazel/rules/score_module/BUILD | 0 bazel/rules/score_module/docs/conf.py | 52 +++ bazel/rules/score_module/docs/index.rst | 331 +++++++++++++++++ bazel/rules/score_module/private/BUILD | 0 bazel/rules/score_module/private/seooc.bzl | 82 +++++ .../private/seooc_sphinx_environment.bzl | 54 +++ bazel/rules/score_module/score_module.bzl | 80 ++++ bazel/rules/score_module/test/BUILD | 91 +++++ bazel/rules/score_module/test/README.md | 137 +++++++ .../test/fixtures/architecture_design.rst | 46 +++ .../test/fixtures/assumptions_of_use.rst | 18 + .../test/fixtures/component_requirements.rst | 30 ++ .../score_module/test/fixtures/index.rst | 13 + .../test/fixtures/safety_analysis.rst | 57 +++ .../score_module/test/score_module_test.bzl | 187 ++++++++++ bazel/rules/score_module/test/seooc_test.bzl | 345 ++++++++++++++++++ 16 files changed, 1523 insertions(+) create mode 100644 bazel/rules/score_module/BUILD create mode 100644 bazel/rules/score_module/docs/conf.py create mode 100644 bazel/rules/score_module/docs/index.rst create mode 100644 bazel/rules/score_module/private/BUILD create mode 100644 bazel/rules/score_module/private/seooc.bzl create mode 100644 bazel/rules/score_module/private/seooc_sphinx_environment.bzl create mode 100644 bazel/rules/score_module/score_module.bzl create mode 100644 bazel/rules/score_module/test/BUILD create mode 100644 bazel/rules/score_module/test/README.md create mode 100644 bazel/rules/score_module/test/fixtures/architecture_design.rst create mode 100644 bazel/rules/score_module/test/fixtures/assumptions_of_use.rst create mode 100644 bazel/rules/score_module/test/fixtures/component_requirements.rst create mode 100644 bazel/rules/score_module/test/fixtures/index.rst create mode 100644 bazel/rules/score_module/test/fixtures/safety_analysis.rst create mode 100644 bazel/rules/score_module/test/score_module_test.bzl create mode 100644 bazel/rules/score_module/test/seooc_test.bzl diff --git a/bazel/rules/score_module/BUILD b/bazel/rules/score_module/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/bazel/rules/score_module/docs/conf.py b/bazel/rules/score_module/docs/conf.py new file mode 100644 index 0000000..a393a5b --- /dev/null +++ b/bazel/rules/score_module/docs/conf.py @@ -0,0 +1,52 @@ +# ******************************************************************************* +# Copyright (c) 2024 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 +# ******************************************************************************* + +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "SCORE MODULE API" +project_url = "https://eclipse-score.github.io/module_template/" +project_prefix = "MODULE_TEMPLATE_" +author = "S-CORE" +version = "0.1" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + + +extensions = [ + "sphinx_design", + "sphinx_needs", + "sphinxcontrib.plantuml", + "score_plantuml", + "score_metamodel", + "score_draw_uml_funcs", + "score_source_code_linker", + "score_layout", +] + +exclude_patterns = [ + "bazel-*", + ".venv_docs", +] + +templates_path = ["templates"] + +# Enable numref +numfig = True diff --git a/bazel/rules/score_module/docs/index.rst b/bazel/rules/score_module/docs/index.rst new file mode 100644 index 0000000..3a31284 --- /dev/null +++ b/bazel/rules/score_module/docs/index.rst @@ -0,0 +1,331 @@ +SCORE Module Bazel Rules +========================= + +This directory contains Bazel rules for defining and building SCORE safety modules following ISO 26262 SEooC (Safety Element out of Context) standards. + +.. contents:: Table of Contents + :depth: 2 + :local: + + +Overview +-------- + +The ``score_module`` package provides Bazel build rules to structure, +validate, and document safety-critical software modules. These rules +integrate with Sphinx documentation generation to produce comprehensive +safety documentation. + +.. uml:: + + @startuml + [SEooC] as SEooC + [bazel module] as bzlmod + Artifact "Assumptions of Use" as AoU + Artifact "(Assumed) Component Requirements" as CR <> + Artifact "Architecture Design" as AD <> + Artifact "Safety Analysis" as SA <> + Card "Implementation" as Impl <> + Card "Testsuite" as Test <> + + + + bzlmod "1" *-- "*" SEooC : contains + SEooC ..> SEooC : depends on + SEooC "1" *-- "1" AoU : has + SEooC "1" *-- "1" CR : has + SEooC "1" *-- "1" AD : has + SEooC "1" *-- "1" Impl : has + SEooC "1" *-- "1" Test : has + SEooC "1" *-- "1" SA : has + + note right of bzlmod + A score_module can contain + one or more Safety Elements + out of Context (SEooC) + end note + + @enduml + + + + + +Rules and Macros +---------------- + +safety_element_out_of_context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**File:** ``score_module.bzl`` + +**Purpose:** Main macro for defining a Safety Element out of Context +(SEooC) module with integrated documentation generation following ISO 26262 +standards. + +**Usage:** + +.. code-block:: python + + safety_element_out_of_context( + name = "my_module", + assumptions_of_use = ":assumptions", + component_requirements = ":requirements", + architectural_design = ":architecture", + safety_analysis = ":safety_analysis", + implementations = [":my_lib", ":my_component"], + tests = [":my_lib_test", ":my_integration_test"], + visibility = ["//visibility:public"] + ) + +**Parameters:** + +- ``name``: The name of the safety element module. Used as the base name + for all generated targets. +- ``assumptions_of_use``: Label to a ``.rst`` or ``.md`` file containing the + Assumptions of Use, which define the safety-relevant operating conditions + and constraints for the SEooC as required by ISO 26262-10 clause 5.4.4. +- ``component_requirements``: Label to a ``.rst`` or ``.md`` file containing + the component requirements specification, defining functional and safety + requirements as required by ISO 26262-3 clause 7. +- ``architectural_design``: Label to a ``.rst`` or ``.md`` file containing + the architectural design specification, describing the software architecture + and design decisions as required by ISO 26262-6 clause 7. +- ``safety_analysis``: Label to a ``.rst`` or ``.md`` file containing the + safety analysis, including FMEA, FMEDA, FTA, or other safety analysis + results as required by ISO 26262-9 clause 8. Documents hazard analysis and + safety measures. +- ``implementations``: List of labels to Bazel targets representing the actual + software implementation (cc_library, cc_binary, etc.) that realizes the + component requirements. This is the source code that implements the safety + functions as required by ISO 26262-6 clause 8. +- ``tests``: List of labels to Bazel test targets (cc_test, py_test, etc.) + that verify the implementation against requirements. Includes unit tests and + integration tests as required by ISO 26262-6 clause 9 for software unit + verification. +- ``visibility``: Bazel visibility specification for the generated SEooC + target. Controls which other packages can depend on this safety element. + +**Generated Targets:** + +This macro creates multiple targets automatically: + +1. ``_index``: Generates index.rst and conf.py files for the module + documentation +2. ``_seooc_index_lib``: Sphinx documentation library for the + generated index +3. ````: The main SEooC target that aggregates all documentation +4. ``.html``: Convenience target to build HTML documentation + +**Implementation Details:** + +The macro orchestrates several internal rules to: + +- Generate a documentation index with a structured table of contents +- Organize documentation files under ``docs/safety_elements//`` +- Integrate assumptions of use and component requirements into a unified documentation structure +- Provide Sphinx-compatible output for HTML generation + +Private Rules +------------- + +seooc +~~~~~ + +**File:** ``private/seooc.bzl`` + +**Purpose:** Internal rule that aggregates safety documentation artifacts +into a Sphinx-compatible structure. + +**Implementation:** ``_seooc_build_impl`` + +**Functionality:** + +- Collects documentation from ``assumptions_of_use`` and + ``component_requirements`` dependencies +- Reorganizes file paths to place artifacts under + ``docs/safety_elements//`` +- Merges the generated index with artifact documentation +- Returns a ``SphinxDocsLibraryInfo`` provider for downstream consumption + +**Attributes:** + +- ``assumptions_of_use``: Label to assumptions of use documentation (mandatory) +- ``component_requirements``: Label to component requirements documentation (mandatory) +- ``index``: Label to the generated index file (mandatory) + +**Output:** + +Returns a ``SphinxDocsLibraryInfo`` provider containing: + +- ``transitive``: Depset of documentation file structs with relocated paths +- ``files``: Empty list (files are in transitive dependencies) + +seooc_sphinx_environment +~~~~~~~~~~~~~~~~~~~~~~~~ + +**File:** ``private/seooc_index.bzl`` + +**Purpose:** Generates the Sphinx environment files (index.rst and conf.py) +for a safety module. + +**Implementation:** ``_seooc_sphinx_environment_impl`` + +**Functionality:** + +- Creates a module-specific ``index.rst`` with: + + - Module name as header (uppercase with underline) + - Table of contents (toctree) linking to all safety artifacts + - References to assumptions of use and component requirements index files + +- Generates a ``conf.py`` configuration file (currently placeholder content) + +**Attributes:** + +- ``module_name``: String name of the module (used for header generation) +- ``assumptions_of_use``: Label to assumptions documentation +- ``component_requirements``: Label to requirements documentation + +**Generated Content Example:** + +.. code-block:: rst + + MY_MODULE + ========= + + .. toctree:: + :maxdepth: 2 + :caption: Contents: + + assumptions_of_use/index + component_requirements/index + architectural_design/index + safety_analysis/index + +**Output:** + +Returns ``DefaultInfo`` with generated ``index.rst`` files. + +Documentation Structure +----------------------- + +When using these rules, documentation is organized in the Bazel sandbox as +follows:: + + docs/ + └── safety_elements/ + └── / + ├── index.rst (generated) + ├── conf.py (generated) + ├── assumptions_of_use/ + │ └── (user-provided documentation) + ├── component_requirements/ + │ └── (user-provided documentation) + ├── architectural_design/ + │ └── (user-provided documentation) + └── safety_analysis/ + └── (user-provided documentation) + + bazel-bin/ + └── / + └── _sources/ + └── (generated documentation sources) + +This structure reflects the file organization created in the Bazel sandbox +during the documentation generation process. The generated ``index.rst`` file +includes a table of contents that references all provided artifacts. + +Integration with Sphinx +------------------------ + +The rules generate ``SphinxDocsLibraryInfo`` providers that are compatible +with ``@rules_python//sphinxdocs``. This enables: + +- Automatic discovery of documentation files by Sphinx +- Proper path relocation for modular documentation +- Transitive dependency handling across multiple safety modules +- HTML, PDF, and other Sphinx output formats + +Usage Example +------------- + +Complete example in a BUILD file: + +.. code-block:: python + + load("@baselibs//bazel/score_module:score_module.bzl", + "safety_element_out_of_context") + + # Documentation artifacts + sphinx_docs_library( + name = "assumptions", + srcs = ["docs/assumptions_of_use.rst"], + ) + + sphinx_docs_library( + name = "requirements", + srcs = ["docs/component_requirements.rst"], + ) + + sphinx_docs_library( + name = "architecture", + srcs = ["docs/architectural_design.rst"], + ) + + sphinx_docs_library( + name = "safety", + srcs = ["docs/safety_analysis.rst"], + ) + + # Implementation targets + cc_library( + name = "lifecycle_lib", + srcs = ["lifecycle_manager.cpp"], + hdrs = ["lifecycle_manager.h"], + ) + + # Test targets + cc_test( + name = "lifecycle_test", + srcs = ["lifecycle_manager_test.cpp"], + deps = [":lifecycle_lib"], + ) + + # Safety Element out of Context + safety_element_out_of_context( + name = "lifecycle_manager_seooc", + assumptions_of_use = ":assumptions", + component_requirements = ":requirements", + architectural_design = ":architecture", + safety_analysis = ":safety", + implementations = [":lifecycle_lib"], + tests = [":lifecycle_test"], + visibility = ["//visibility:public"], + ) + +Then build the documentation: + +.. code-block:: bash + + # Build the SEooC target + bazel build //:lifecycle_manager_seooc + + +Dependencies +------------ + +- ``@rules_python//sphinxdocs``: Sphinx documentation build rules +- ``SphinxDocsLibraryInfo``: Provider for documentation artifacts + +Design Rationale +---------------- + +These rules enforce a structured approach to safety documentation by: + +1. **Standardization**: All safety modules follow the same documentation + structure +2. **Traceability**: Build system ensures all required artifacts are present +3. **Modularity**: Documentation can be composed from multiple sources +4. **Automation**: Index generation and path management are automated +5. **Integration**: Seamless integration with existing Sphinx workflows diff --git a/bazel/rules/score_module/private/BUILD b/bazel/rules/score_module/private/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/bazel/rules/score_module/private/seooc.bzl b/bazel/rules/score_module/private/seooc.bzl new file mode 100644 index 0000000..d0bfbe6 --- /dev/null +++ b/bazel/rules/score_module/private/seooc.bzl @@ -0,0 +1,82 @@ +load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo") + +seooc_artifacts = { + "assumptions_of_use": attr.label( + providers = [SphinxDocsLibraryInfo], + mandatory = True, + doc = "Label to a sphinx_docs_library target containing the Assumptions of Use, which define the safety-relevant operating conditions and constraints for the SEooC as required by ISO 26262-10 clause 5.4.4.", + ), + "component_requirements": attr.label( + providers = [SphinxDocsLibraryInfo], + mandatory = True, + doc = "Label to a sphinx_docs_library target containing the component requirements specification, defining functional and safety requirements as required by ISO 26262-3 clause 7.", + ), + "architectural_design": attr.label( + providers = [SphinxDocsLibraryInfo], + mandatory = True, + doc = "Label to a sphinx_docs_library target containing the architectural design specification, describing the software architecture and design decisions as required by ISO 26262-6 clause 7.", + ), + "safety_analysis": attr.label( + providers = [SphinxDocsLibraryInfo], + mandatory = True, + doc = "Label to a sphinx_docs_library target containing the safety analysis, including FMEA, FMEDA, FTA, or other safety analysis results as required by ISO 26262-9 clause 8. Documents hazard analysis and safety measures.", + ), +} + +seooc_targets = { + "implementations": attr.label( + mandatory = False, + doc = "", + ), + "tests": attr.label( + mandatory = False, + doc = "", + ), +} + +def _seooc_build_impl(ctx): + """Implementation of safety_element build rule for ISO 26262 SEooC.""" + + all_files = [] + for artifact in seooc_artifacts: + dep = getattr(ctx.attr, artifact) + for t in dep[SphinxDocsLibraryInfo].transitive.to_list(): + entry = struct( + strip_prefix = t.strip_prefix, + prefix = "docs/safety_elements/" + ctx.attr.name + "/" + t.prefix, + files = t.files, + ) + all_files.append(entry) + + index = ctx.attr.index + for t in index[SphinxDocsLibraryInfo].transitive.to_list(): + entry = struct( + strip_prefix = t.strip_prefix, + prefix = "", + files = t.files, + ) + all_files.append(entry) + + result = depset(all_files) + return [ + DefaultInfo( + files = depset([]), + ), + SphinxDocsLibraryInfo( + strip_prefix = "", + prefix = "", + files = [], + transitive = result, + ), + ] + +seooc = rule( + implementation = _seooc_build_impl, + attrs = seooc_artifacts | { + "index": attr.label( + allow_files = [".rst", ".md", ".py"], + mandatory = True, + doc = "", + ), + }, +) diff --git a/bazel/rules/score_module/private/seooc_sphinx_environment.bzl b/bazel/rules/score_module/private/seooc_sphinx_environment.bzl new file mode 100644 index 0000000..9d1738b --- /dev/null +++ b/bazel/rules/score_module/private/seooc_sphinx_environment.bzl @@ -0,0 +1,54 @@ +load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo") +load("//bazel/rules/score_module/private:seooc.bzl", "seooc_artifacts") + +index_content = """ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +""" + +def _seooc_sphinx_environment_impl(ctx): + """Generate the index.rst file for a seooc""" + + index_rst = ctx.actions.declare_file("docs/safety_elements/" + ctx.attr.module_name + "/index.rst") + + header = ctx.attr.module_name.upper() + header += "\n" + "=" * len(header) + + file_content = header + index_content + + for artifact in seooc_artifacts: + attr = getattr(ctx.attr, artifact) + if attr: + # Get all files from the SphinxDocsLibraryInfo + src_files = list(attr[SphinxDocsLibraryInfo].files) + if src_files: + # Use the first file (typically the main documentation file) + artifact_index_file = src_files[0] + + # Create link path from the file + link = artifact_index_file.short_path.replace(".rst", "").replace(".md", "") + if ctx.label.package: + print("replacing link: " + ctx.label.package + "/") + link = link.replace(ctx.label.package + "/", "") + file_content += " " + link + "\n" + + ctx.actions.write( + output = index_rst, + content = file_content, + ) + + return ( + DefaultInfo( + files = depset([index_rst]), + ) + ) + +seooc_sphinx_environment = rule( + implementation = _seooc_sphinx_environment_impl, + attrs = seooc_artifacts | { + "module_name": attr.string(), + }, +) diff --git a/bazel/rules/score_module/score_module.bzl b/bazel/rules/score_module/score_module.bzl new file mode 100644 index 0000000..6ed760a --- /dev/null +++ b/bazel/rules/score_module/score_module.bzl @@ -0,0 +1,80 @@ +load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_docs") +load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library") +load("//bazel/rules/score_module/private:seooc.bzl", "seooc") +load("//bazel/rules/score_module/private:seooc_sphinx_environment.bzl", "seooc_sphinx_environment") + +def safety_element_out_of_context( + name, + assumptions_of_use, + component_requirements, + architectural_design, + safety_analysis, + implementations, + tests, + visibility): + """Defines a Safety Element out of Context (SEooC) following ISO 26262 standards. + + This macro creates a complete SEooC module with integrated documentation generation + using Sphinx. It packages all required ISO 26262 artifacts and generates HTML + documentation for safety certification. + + Args: + name: The name of the safety element module. Used as the base name for all + generated targets. + assumptions_of_use: Label to a .rst or .md file containing the Assumptions of Use, + which define the safety-relevant operating conditions and constraints for the + SEooC as required by ISO 26262-10 clause 5.4.4. + component_requirements: Label to a .rst or .md file containing the component + requirements specification, defining functional and safety requirements as + required by ISO 26262-3 clause 7. + architectural_design: Label to a .rst or .md file containing the architectural + design specification, describing the software architecture and design decisions + as required by ISO 26262-6 clause 7. + safety_analysis: Label to a .rst or .md file containing the safety analysis, + including FMEA, FMEDA, FTA, or other safety analysis results as required by + ISO 26262-9 clause 8. Documents hazard analysis and safety measures. + implementations: List of labels to Bazel targets representing the actual software + implementation (cc_library, cc_binary, etc.) that realizes the component + requirements. This is the source code that implements the safety functions + as required by ISO 26262-6 clause 8. + tests: List of labels to Bazel test targets (cc_test, py_test, etc.) that verify + the implementation against requirements. Includes unit tests and integration + tests as required by ISO 26262-6 clause 9 for software unit verification. + visibility: Bazel visibility specification for the generated SEooC target. Controls + which other packages can depend on this safety element. + + Generated Targets: + _index: Sphinx environment with generated index.rst and conf.py files + _seooc_index_lib: Sphinx documentation library for the module + : Main SEooC target aggregating all documentation + .html: HTML documentation output + """ + + # Generate index file for the seooc documentation + seooc_sphinx_environment( + name = name + "_index", + module_name = name, + assumptions_of_use = assumptions_of_use, + component_requirements = component_requirements, + architectural_design = architectural_design, + safety_analysis = safety_analysis, + ) + + sphinx_docs_library( + name = name + "_seooc_index_lib", + srcs = [name + "_index"], + prefix = "", + visibility = ["//visibility:public"], + deps = [], + ) + + # Create the main SEooC target + seooc( + name = name, + index = name + "_seooc_index_lib", + assumptions_of_use = assumptions_of_use, + component_requirements = component_requirements, + architectural_design = architectural_design, + safety_analysis = safety_analysis, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/test/BUILD b/bazel/rules/score_module/test/BUILD new file mode 100644 index 0000000..95d7b65 --- /dev/null +++ b/bazel/rules/score_module/test/BUILD @@ -0,0 +1,91 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* +load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library") +load("//bazel/rules/score_module:score_module.bzl", "safety_element_out_of_context") +load("//bazel/rules/score_module/private:seooc.bzl", "seooc") +load(":score_module_test.bzl", "safety_element_macro_test_suite") +load(":seooc_test.bzl", "seooc_test_suite") + +package(default_visibility = ["//visibility:public"]) + +# Test fixtures for seooc tests +sphinx_docs_library( + name = "test_assumptions", + srcs = ["fixtures/assumptions_of_use.rst"], + tags = ["manual"], +) + +sphinx_docs_library( + name = "test_requirements", + srcs = ["fixtures/component_requirements.rst"], + tags = ["manual"], +) + +sphinx_docs_library( + name = "test_architecture", + srcs = ["fixtures/architecture_design.rst"], + tags = ["manual"], +) + +sphinx_docs_library( + name = "test_safety", + srcs = ["fixtures/safety_analysis.rst"], + tags = ["manual"], +) + +sphinx_docs_library( + name = "test_index_lib", + srcs = ["fixtures/index.rst"], + tags = ["manual"], +) + +# Minimal seooc test target with all mandatory attributes +seooc( + name = "test_seooc_minimal", + architectural_design = ":test_architecture", + assumptions_of_use = ":test_assumptions", + component_requirements = ":test_requirements", + index = ":test_index_lib", + safety_analysis = ":test_safety", + tags = ["manual"], +) + +# Complete seooc test target with all mandatory attributes +seooc( + name = "test_seooc_complete", + architectural_design = ":test_architecture", + assumptions_of_use = ":test_assumptions", + component_requirements = ":test_requirements", + index = ":test_index_lib", + safety_analysis = ":test_safety", + tags = ["manual"], +) + +# Run the test suite +seooc_test_suite(name = "seooc_tests") + +# Test target using the safety_element_out_of_context macro +# Uses sphinx_docs_library targets that provide SphinxDocsLibraryInfo +safety_element_out_of_context( + name = "test_macro_seooc", + architectural_design = ":test_architecture", + assumptions_of_use = ":test_assumptions", + component_requirements = ":test_requirements", + implementations = None, + safety_analysis = ":test_safety", + tests = None, + visibility = ["//visibility:public"], +) + +# Run the macro test suite +safety_element_macro_test_suite(name = "macro_tests") diff --git a/bazel/rules/score_module/test/README.md b/bazel/rules/score_module/test/README.md new file mode 100644 index 0000000..f85cdc9 --- /dev/null +++ b/bazel/rules/score_module/test/README.md @@ -0,0 +1,137 @@ +# SEooC Rule Tests + +This directory contains unit tests for the `seooc` rule, which is used to define Safety Elements out of Context (SEooC) following ISO 26262 standards. + +## Test Suite Overview + +The test suite (`seooc_test.bzl`) uses Bazel Skylib's `unittest` framework to verify the correctness of the `seooc` rule implementation. The tests are organized into several categories: + +### Test Categories + +1. **Provider Tests** (`seooc_providers_test`) + - Verifies that the `seooc` rule provides the required providers + - Checks for `DefaultInfo` provider + - Checks for `SphinxDocsLibraryInfo` provider + +2. **Transitive Documentation Tests** (`seooc_transitive_docs_test`) + - Verifies that the rule correctly aggregates transitive documentation + - Ensures that the transitive field is a depset + - Validates the structure of transitive documentation entries + - Confirms that documentation paths start with `docs/safety_elements/` + +3. **Attribute Handling Tests** (`seooc_attributes_test`) + - Verifies that mandatory attributes are correctly handled + - Ensures that at least index documentation is present + +4. **Path Prefixing Tests** (`seooc_path_prefixing_test`) + - Verifies that documentation paths are correctly prefixed with the module name + - Ensures proper path organization for Sphinx documentation + +## Test Fixtures + +The test suite uses the following fixture files located in `fixtures/`: + +- **`assumptions_of_use.rst`**: Sample assumptions of use document +- **`component_requirements.rst`**: Sample component requirements document +- **`index.rst`**: Sample index file for documentation structure + +These fixtures are wrapped as `sphinx_docs_library` targets in the `BUILD` file to create realistic test scenarios. + +## Running the Tests + +To run all seooc tests: + +```bash +bazel test //bazel/rules/score_module/test:seooc_tests +``` + +To run with verbose output: + +```bash +bazel test //bazel/rules/score_module/test:seooc_tests --test_output=all +``` + +To run individual tests: + +```bash +bazel test //bazel/rules/score_module/test:seooc_providers_test +bazel test //bazel/rules/score_module/test:seooc_transitive_docs_test +bazel test //bazel/rules/score_module/test:seooc_attributes_test +bazel test //bazel/rules/score_module/test:seooc_path_prefixing_test +``` + +## Test Targets + +The `BUILD` file defines two test targets: + +1. **`test_seooc_minimal`**: Tests the seooc rule with minimal required attributes + - Only includes `assumptions_of_use` and `index` + - Verifies basic functionality + +2. **`test_seooc_complete`**: Tests the seooc rule with all optional attributes + - Includes all optional documentation attributes + - Verifies handling of complete documentation sets + +## Adding New Tests + +To add a new test: + +1. Define a test implementation function in `seooc_test.bzl`: + + ```python + def _my_new_test_impl(ctx): + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Your test assertions here + asserts.true(env, condition, "error message") + + return analysistest.end(env) + + my_new_test = analysistest.make(_my_new_test_impl) + ``` + +2. Add the test to `_test_seooc()` function: + + ```python + my_new_test( + name = "my_new_test", + target_under_test = ":test_seooc_minimal", + ) + ``` + +3. Include it in the test suite: + + ```python + native.test_suite( + name = name, + tests = [ + # ... existing tests ... + ":my_new_test", + ], + ) + ``` + +## Test Coverage + +The current test suite covers: + +- ✅ Provider generation +- ✅ Transitive documentation aggregation +- ✅ Mandatory attribute handling +- ✅ Path prefixing with module names +- ✅ Optional attribute handling + +## Dependencies + +The tests depend on: + +- `@bazel_skylib//lib:unittest` - Bazel Skylib testing framework +- `@rules_python//sphinxdocs` - Sphinx documentation rules +- `//bazel/rules/score_module/private:seooc.bzl` - The rule being tested + +## Notes + +- All test targets are tagged with `"manual"` to prevent them from being built during normal builds +- Tests use the `analysistest` framework, which performs analysis-phase validation +- The test suite is part of the continuous integration pipeline diff --git a/bazel/rules/score_module/test/fixtures/architecture_design.rst b/bazel/rules/score_module/test/fixtures/architecture_design.rst new file mode 100644 index 0000000..c73ce0e --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/architecture_design.rst @@ -0,0 +1,46 @@ +Architecture Design +=================== + +This document describes the architectural design of the test SEooC module. + +System Architecture +------------------- + +The system consists of the following components: + +* Input Processing Module +* Data Processing Engine +* Output Handler +* Fault Detection and Handling + +Component Interfaces +--------------------- + +Input Processing Module +~~~~~~~~~~~~~~~~~~~~~~~ + +* **Input**: Raw sensor data +* **Output**: Validated and formatted data +* **Interface**: I2C/SPI bus + +Data Processing Engine +~~~~~~~~~~~~~~~~~~~~~~ + +* **Input**: Validated data from Input Processing Module +* **Output**: Processed results +* **Interface**: Internal memory-mapped registers + +Design Decisions +---------------- + +Decision 1: Use of Hardware Watchdog +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The architecture includes a hardware watchdog timer to ensure system reliability +and meet safety requirements REQ-SAFE-001. + +Decision 2: Redundant Processing Paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Critical calculations are performed using redundant processing paths to detect +and prevent silent data corruption. diff --git a/bazel/rules/score_module/test/fixtures/assumptions_of_use.rst b/bazel/rules/score_module/test/fixtures/assumptions_of_use.rst new file mode 100644 index 0000000..11f3785 --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/assumptions_of_use.rst @@ -0,0 +1,18 @@ +Assumptions of Use +================== + +This document describes the assumptions of use for the test SEooC module. + +Operating Conditions +-------------------- + +* Operating temperature: -40°C to +85°C +* Supply voltage: 12V ±10% +* Maximum processing load: 80% + +Environmental Assumptions +------------------------- + +* The system operates in a controlled environment +* No exposure to extreme weather conditions +* Regular maintenance is performed diff --git a/bazel/rules/score_module/test/fixtures/component_requirements.rst b/bazel/rules/score_module/test/fixtures/component_requirements.rst new file mode 100644 index 0000000..46c38ca --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/component_requirements.rst @@ -0,0 +1,30 @@ +Component Requirements +====================== + +This document defines the functional and safety requirements. + +Functional Requirements +------------------------ + +REQ-FUNC-001 +~~~~~~~~~~~~ + +The system shall process input data within 100ms. + +REQ-FUNC-002 +~~~~~~~~~~~~ + +The system shall provide output with 99.9% accuracy. + +Safety Requirements +------------------- + +REQ-SAFE-001 +~~~~~~~~~~~~ + +The system shall detect and handle fault conditions within 50ms. + +REQ-SAFE-002 +~~~~~~~~~~~~ + +The system shall maintain safe state during power loss. diff --git a/bazel/rules/score_module/test/fixtures/index.rst b/bazel/rules/score_module/test/fixtures/index.rst new file mode 100644 index 0000000..01c37cf --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/index.rst @@ -0,0 +1,13 @@ +Test Safety Element +=================== + +This is a test index file for the SEooC test suite. + +Contents +-------- + +.. toctree:: + :maxdepth: 2 + + assumptions_of_use + component_requirements diff --git a/bazel/rules/score_module/test/fixtures/safety_analysis.rst b/bazel/rules/score_module/test/fixtures/safety_analysis.rst new file mode 100644 index 0000000..54b8908 --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/safety_analysis.rst @@ -0,0 +1,57 @@ +Safety Analysis +=============== + +This document contains the safety analysis for the test SEooC module. + +Failure Mode and Effects Analysis (FMEA) +----------------------------------------- + +FMEA-001: Input Data Corruption +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* **Failure Mode**: Corrupted input data from sensors +* **Effect**: Incorrect processing results +* **Severity**: High +* **Detection Method**: CRC checksum validation +* **Mitigation**: Reject invalid data and enter safe state + +FMEA-002: Processing Timeout +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* **Failure Mode**: Processing exceeds time deadline +* **Effect**: System becomes unresponsive +* **Severity**: Medium +* **Detection Method**: Watchdog timer +* **Mitigation**: System reset and recovery + +Fault Tree Analysis (FTA) +-------------------------- + +Top Event: System Failure +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following events can lead to system failure: + +* Hardware failure (probability: 1e-6) +* Software defect (probability: 1e-5) +* External interference (probability: 1e-7) + +**Total failure probability**: 1.11e-5 per hour + +Safety Measures +--------------- + +SM-001: Input Validation +~~~~~~~~~~~~~~~~~~~~~~~~~ + +All input data is validated before processing to prevent invalid data propagation. + +SM-002: Periodic Self-Test +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The system performs periodic self-tests to detect latent faults. + +SM-003: Safe State Transition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Upon detection of critical faults, the system transitions to a predefined safe state. diff --git a/bazel/rules/score_module/test/score_module_test.bzl b/bazel/rules/score_module/test/score_module_test.bzl new file mode 100644 index 0000000..46092b9 --- /dev/null +++ b/bazel/rules/score_module/test/score_module_test.bzl @@ -0,0 +1,187 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* +"""Unit tests for the safety_element_out_of_context macro.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo") + +# Test that the macro generates the expected targets +def _macro_generates_targets_test_impl(ctx): + """Test that safety_element_out_of_context macro generates all expected targets.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # The main target should exist and provide the required providers + asserts.true( + env, + DefaultInfo in target_under_test, + "Main target should provide DefaultInfo", + ) + + asserts.true( + env, + SphinxDocsLibraryInfo in target_under_test, + "Main target should provide SphinxDocsLibraryInfo", + ) + + return analysistest.end(env) + +macro_generates_targets_test = analysistest.make(_macro_generates_targets_test_impl) + +# Test that the macro correctly creates the index library target +def _macro_index_lib_test_impl(ctx): + """Test that the macro creates the index library target.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + + # Verify that transitive documentation includes the index + transitive_list = sphinx_info.transitive.to_list() + asserts.true( + env, + len(transitive_list) > 0, + "Macro should generate documentation with index", + ) + + return analysistest.end(env) + +macro_index_lib_test = analysistest.make(_macro_index_lib_test_impl) + +# Test that the macro properly aggregates all documentation artifacts +def _macro_doc_aggregation_test_impl(ctx): + """Test that the macro aggregates all documentation artifacts.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + transitive_list = sphinx_info.transitive.to_list() + + # Should have documentation entries for: + # - index + # - assumptions_of_use + # - component_requirements + # - architectural_design + # - safety_analysis + # That's at least 5 entries (could be more with nested dependencies) + asserts.true( + env, + len(transitive_list) >= 5, + "Macro should aggregate all documentation artifacts (expected at least 5, got {})".format(len(transitive_list)), + ) + + return analysistest.end(env) + +macro_doc_aggregation_test = analysistest.make(_macro_doc_aggregation_test_impl) + +# Test that the macro correctly prefixes documentation paths +def _macro_path_structure_test_impl(ctx): + """Test that the macro creates correct documentation path structure.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + transitive_list = sphinx_info.transitive.to_list() + + # Extract the module name from the target label + module_name = target_under_test.label.name + + # Check that documentation paths follow the expected structure + found_correct_structure = False + for entry in transitive_list: + if "docs/safety_elements/" in entry.prefix and module_name in entry.prefix: + found_correct_structure = True + break + + asserts.true( + env, + found_correct_structure, + "Documentation paths should follow 'docs/safety_elements//' structure", + ) + + return analysistest.end(env) + +macro_path_structure_test = analysistest.make(_macro_path_structure_test_impl) + +# Test that the macro handles visibility correctly +def _macro_visibility_test_impl(ctx): + """Test that the macro correctly handles visibility attribute.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # If the target can be accessed in the test, visibility is working + asserts.true( + env, + target_under_test != None, + "Target should be accessible according to visibility settings", + ) + + return analysistest.end(env) + +macro_visibility_test = analysistest.make(_macro_visibility_test_impl) + +# Test suite setup function +def _test_safety_element_macro(): + """Creates test targets for the safety_element_out_of_context macro.""" + + # Test 1: Verify macro generates expected targets + macro_generates_targets_test( + name = "macro_generates_targets_test", + target_under_test = ":test_macro_seooc", + ) + + # Test 2: Verify index library generation + macro_index_lib_test( + name = "macro_index_lib_test", + target_under_test = ":test_macro_seooc", + ) + + # Test 3: Verify documentation aggregation + macro_doc_aggregation_test( + name = "macro_doc_aggregation_test", + target_under_test = ":test_macro_seooc", + ) + + # Test 4: Verify path structure + macro_path_structure_test( + name = "macro_path_structure_test", + target_under_test = ":test_macro_seooc", + ) + + # Test 5: Verify visibility handling + macro_visibility_test( + name = "macro_visibility_test", + target_under_test = ":test_macro_seooc", + ) + +def safety_element_macro_test_suite(name): + """Creates a test suite for the safety_element_out_of_context macro. + + Args: + name: The name of the test suite. + """ + _test_safety_element_macro() + + native.test_suite( + name = name, + tests = [ + ":macro_generates_targets_test", + ":macro_index_lib_test", + ":macro_doc_aggregation_test", + ":macro_path_structure_test", + ":macro_visibility_test", + ], + ) diff --git a/bazel/rules/score_module/test/seooc_test.bzl b/bazel/rules/score_module/test/seooc_test.bzl new file mode 100644 index 0000000..a417fed --- /dev/null +++ b/bazel/rules/score_module/test/seooc_test.bzl @@ -0,0 +1,345 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* +"""Unit tests for the seooc rule.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo") + +# Test that the seooc rule creates the correct providers +def _seooc_providers_test_impl(ctx): + """Test that seooc rule provides DefaultInfo and SphinxDocsLibraryInfo.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check that DefaultInfo is provided + asserts.true( + env, + DefaultInfo in target_under_test, + "seooc rule should provide DefaultInfo", + ) + + # Check that SphinxDocsLibraryInfo is provided + asserts.true( + env, + SphinxDocsLibraryInfo in target_under_test, + "seooc rule should provide SphinxDocsLibraryInfo", + ) + + return analysistest.end(env) + +seooc_providers_test = analysistest.make(_seooc_providers_test_impl) + +# Test that the seooc rule correctly aggregates transitive documentation +def _seooc_transitive_docs_test_impl(ctx): + """Test that seooc rule correctly aggregates transitive documentation.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + + # Check that transitive field is a depset + asserts.true( + env, + type(sphinx_info.transitive) == type(depset([])), + "SphinxDocsLibraryInfo.transitive should be a depset", + ) + + # Check that transitive documentation is aggregated + transitive_list = sphinx_info.transitive.to_list() + asserts.true( + env, + len(transitive_list) > 0, + "seooc should aggregate transitive documentation", + ) + + # Verify that each entry has required fields + for entry in transitive_list: + asserts.true( + env, + hasattr(entry, "strip_prefix"), + "Each transitive entry should have strip_prefix field", + ) + asserts.true( + env, + hasattr(entry, "prefix"), + "Each transitive entry should have prefix field", + ) + asserts.true( + env, + hasattr(entry, "files"), + "Each transitive entry should have files field", + ) + + # Check that prefix either starts with the expected path or is empty (for index) + asserts.true( + env, + entry.prefix.startswith("docs/safety_elements/") or entry.prefix == "", + "Documentation prefix should start with 'docs/safety_elements/' or be empty for index", + ) + + return analysistest.end(env) + +seooc_transitive_docs_test = analysistest.make(_seooc_transitive_docs_test_impl) + +# Test that the seooc rule correctly handles mandatory and optional attributes +def _seooc_attributes_test_impl(ctx): + """Test that seooc rule correctly handles mandatory attributes.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + + # Verify that documentation files are present + transitive_list = sphinx_info.transitive.to_list() + + # There should be at least documentation from the index + asserts.true( + env, + len(transitive_list) >= 1, + "seooc should have at least index documentation", + ) + + return analysistest.end(env) + +seooc_attributes_test = analysistest.make(_seooc_attributes_test_impl) + +# Test that seooc properly prefixes paths with module name +def _seooc_path_prefixing_test_impl(ctx): + """Test that seooc rule correctly prefixes paths with module name.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + transitive_list = sphinx_info.transitive.to_list() + + # Extract the module name from the target label + module_name = target_under_test.label.name + + # Check that at least one entry has the correct prefix + found_correct_prefix = False + for entry in transitive_list: + if module_name in entry.prefix: + found_correct_prefix = True + break + + asserts.true( + env, + found_correct_prefix, + "At least one documentation entry should contain the module name in its prefix", + ) + + return analysistest.end(env) + +seooc_path_prefixing_test = analysistest.make(_seooc_path_prefixing_test_impl) + +# Test that seooc properly processes assumptions_of_use attribute +def _seooc_has_assumptions_test_impl(ctx): + """Test that seooc rule properly processes assumptions_of_use attribute.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + transitive_list = sphinx_info.transitive.to_list() + + # Verify that documentation is present (should include assumptions) + asserts.true( + env, + len(transitive_list) > 0, + "seooc should include assumptions_of_use documentation", + ) + + return analysistest.end(env) + +seooc_has_assumptions_test = analysistest.make(_seooc_has_assumptions_test_impl) + +# Test that seooc properly processes component_requirements attribute +def _seooc_has_requirements_test_impl(ctx): + """Test that seooc rule properly processes component_requirements attribute.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + transitive_list = sphinx_info.transitive.to_list() + + # Verify that documentation is present (should include requirements) + asserts.true( + env, + len(transitive_list) > 0, + "seooc should include component_requirements documentation", + ) + + return analysistest.end(env) + +seooc_has_requirements_test = analysistest.make(_seooc_has_requirements_test_impl) + +# Test that seooc properly processes architectural_design attribute +def _seooc_has_architecture_test_impl(ctx): + """Test that seooc rule properly processes architectural_design attribute.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + transitive_list = sphinx_info.transitive.to_list() + + # Verify that documentation is present (should include architecture) + asserts.true( + env, + len(transitive_list) > 0, + "seooc should include architectural_design documentation", + ) + + return analysistest.end(env) + +seooc_has_architecture_test = analysistest.make(_seooc_has_architecture_test_impl) + +# Test that seooc properly processes safety_analysis attribute +def _seooc_has_safety_test_impl(ctx): + """Test that seooc rule properly processes safety_analysis attribute.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + transitive_list = sphinx_info.transitive.to_list() + + # Verify that documentation is present (should include safety analysis) + asserts.true( + env, + len(transitive_list) > 0, + "seooc should include safety_analysis documentation", + ) + + return analysistest.end(env) + +seooc_has_safety_test = analysistest.make(_seooc_has_safety_test_impl) + +# Test that seooc properly processes index attribute +def _seooc_has_index_test_impl(ctx): + """Test that seooc rule properly processes index attribute.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the SphinxDocsLibraryInfo provider + sphinx_info = target_under_test[SphinxDocsLibraryInfo] + transitive_list = sphinx_info.transitive.to_list() + + # Verify that documentation is present (should include index) + asserts.true( + env, + len(transitive_list) > 0, + "seooc should include index documentation", + ) + + return analysistest.end(env) + +seooc_has_index_test = analysistest.make(_seooc_has_index_test_impl) + +# Test suite setup function +def _test_seooc(): + """Creates test targets for the seooc rule.""" + + # Test 1: Verify providers + seooc_providers_test( + name = "seooc_providers_test", + target_under_test = ":test_seooc_minimal", + ) + + # Test 2: Verify transitive documentation aggregation + seooc_transitive_docs_test( + name = "seooc_transitive_docs_test", + target_under_test = ":test_seooc_minimal", + ) + + # Test 3: Verify attribute handling + seooc_attributes_test( + name = "seooc_attributes_test", + target_under_test = ":test_seooc_minimal", + ) + + # Test 4: Verify path prefixing + seooc_path_prefixing_test( + name = "seooc_path_prefixing_test", + target_under_test = ":test_seooc_minimal", + ) + + # Test with complete attributes + seooc_providers_test( + name = "seooc_providers_test_complete", + target_under_test = ":test_seooc_complete", + ) + + seooc_transitive_docs_test( + name = "seooc_transitive_docs_test_complete", + target_under_test = ":test_seooc_complete", + ) + + # Test 5: Verify assumptions_of_use attribute is processed + seooc_has_assumptions_test( + name = "seooc_has_assumptions_test", + target_under_test = ":test_seooc_complete", + ) + + # Test 6: Verify component_requirements attribute is processed + seooc_has_requirements_test( + name = "seooc_has_requirements_test", + target_under_test = ":test_seooc_complete", + ) + + # Test 7: Verify architectural_design attribute is processed + seooc_has_architecture_test( + name = "seooc_has_architecture_test", + target_under_test = ":test_seooc_complete", + ) + + # Test 8: Verify safety_analysis attribute is processed + seooc_has_safety_test( + name = "seooc_has_safety_test", + target_under_test = ":test_seooc_complete", + ) + + # Test 9: Verify index attribute is processed + seooc_has_index_test( + name = "seooc_has_index_test", + target_under_test = ":test_seooc_complete", + ) + +def seooc_test_suite(name): + """Creates a test suite for the seooc rule. + + Args: + name: The name of the test suite. + """ + _test_seooc() + + native.test_suite( + name = name, + tests = [ + ":seooc_providers_test", + ":seooc_transitive_docs_test", + ":seooc_attributes_test", + ":seooc_path_prefixing_test", + ":seooc_providers_test_complete", + ":seooc_transitive_docs_test_complete", + ":seooc_has_assumptions_test", + ":seooc_has_requirements_test", + ":seooc_has_architecture_test", + ":seooc_has_safety_test", + ":seooc_has_index_test", + ], + ) From 73720012bfec7417034dc879c2fd5918b67299dd Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Thu, 18 Dec 2025 17:21:35 +0100 Subject: [PATCH 2/9] [Module API] Introduce build per module Incorporated first feedback. Introduced a modular sphinx-build. --- .bazelrc | 5 + MODULE.bazel | 10 + bazel/rules/score_module/BUILD | 50 ++ bazel/rules/score_module/docs/conf.py | 52 -- bazel/rules/score_module/docs/index.rst | 545 +++++++++++------- .../score_module/private/score_module.bzl | 299 ++++++++++ .../score_module/private/score_seooc.bzl | 237 ++++++++ bazel/rules/score_module/private/seooc.bzl | 88 +-- .../private/seooc_sphinx_environment.bzl | 54 -- bazel/rules/score_module/score_module.bzl | 89 +-- .../score_module/src/sphinx_html_merge.py | 191 ++++++ .../rules/score_module/src/sphinx_wrapper.py | 231 ++++++++ .../score_module/templates/conf.template.py | 192 ++++++ .../templates/seooc_index.template.rst | 26 + bazel/rules/score_module/test/BUILD | 215 +++++-- bazel/rules/score_module/test/README.md | 137 ----- .../test/fixtures/architecture_design.rst | 46 -- .../test/fixtures/assumptions_of_use.rst | 18 - .../test/fixtures/component_requirements.rst | 30 - .../score_module/test/fixtures/index.rst | 13 - .../test/fixtures/module_a/index.rst | 31 + .../test/fixtures/module_b/index.rst | 37 ++ .../test/fixtures/module_c/index.rst | 29 + .../test/fixtures/safety_analysis.rst | 57 -- .../seooc_test/architectural_design.rst | 174 ++++++ .../seooc_test/assumptions_of_use.rst | 80 +++ .../seooc_test/component_requirements.rst | 105 ++++ .../fixtures/seooc_test/safety_analysis.rst | 292 ++++++++++ .../test/html_generation_test.bzl | 223 +++++++ .../test/score_module_providers_test.bzl | 323 +++++++++++ .../score_module/test/score_module_test.bzl | 187 ------ bazel/rules/score_module/test/seooc_test.bzl | 367 +++--------- 32 files changed, 3113 insertions(+), 1320 deletions(-) delete mode 100644 bazel/rules/score_module/docs/conf.py create mode 100644 bazel/rules/score_module/private/score_module.bzl create mode 100644 bazel/rules/score_module/private/score_seooc.bzl delete mode 100644 bazel/rules/score_module/private/seooc_sphinx_environment.bzl create mode 100644 bazel/rules/score_module/src/sphinx_html_merge.py create mode 100644 bazel/rules/score_module/src/sphinx_wrapper.py create mode 100644 bazel/rules/score_module/templates/conf.template.py create mode 100644 bazel/rules/score_module/templates/seooc_index.template.rst delete mode 100644 bazel/rules/score_module/test/README.md delete mode 100644 bazel/rules/score_module/test/fixtures/architecture_design.rst delete mode 100644 bazel/rules/score_module/test/fixtures/assumptions_of_use.rst delete mode 100644 bazel/rules/score_module/test/fixtures/component_requirements.rst delete mode 100644 bazel/rules/score_module/test/fixtures/index.rst create mode 100644 bazel/rules/score_module/test/fixtures/module_a/index.rst create mode 100644 bazel/rules/score_module/test/fixtures/module_b/index.rst create mode 100644 bazel/rules/score_module/test/fixtures/module_c/index.rst delete mode 100644 bazel/rules/score_module/test/fixtures/safety_analysis.rst create mode 100644 bazel/rules/score_module/test/fixtures/seooc_test/architectural_design.rst create mode 100644 bazel/rules/score_module/test/fixtures/seooc_test/assumptions_of_use.rst create mode 100644 bazel/rules/score_module/test/fixtures/seooc_test/component_requirements.rst create mode 100644 bazel/rules/score_module/test/fixtures/seooc_test/safety_analysis.rst create mode 100644 bazel/rules/score_module/test/html_generation_test.bzl create mode 100644 bazel/rules/score_module/test/score_module_providers_test.bzl delete mode 100644 bazel/rules/score_module/test/score_module_test.bzl diff --git a/.bazelrc b/.bazelrc index 0823a01..3a6ddac 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,2 +1,7 @@ common --registry=https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/ common --registry=https://bcr.bazel.build + +build --java_language_version=17 +build --tool_java_language_version=17 +build --java_runtime_version=remotejdk_17 +build --tool_java_runtime_version=remotejdk_17 diff --git a/MODULE.bazel b/MODULE.bazel index d193ef2..60d314e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -95,3 +95,13 @@ multitool.hub( lockfile = "tools/yamlfmt.lock.json", ) use_repo(multitool, "yamlfmt_hub") + +bazel_dep(name = "score_docs_as_code", version = "2.2.0") +git_override( + module_name = "score_docs_as_code", + commit = "be61c922ef9c3bf70dc7d3a5a1cb0c14f6e95d20", + remote = "https://github.com/eclipse-score/docs-as-code.git", +) + +bazel_dep(name = "score_platform", version = "0.5.0") +bazel_dep(name = "score_process", version = "1.3.2") diff --git a/bazel/rules/score_module/BUILD b/bazel/rules/score_module/BUILD index e69de29..43cfb91 100644 --- a/bazel/rules/score_module/BUILD +++ b/bazel/rules/score_module/BUILD @@ -0,0 +1,50 @@ +load( + "//bazel/rules/score_module:score_module.bzl", + "score_module", +) + +exports_files([ + "templates/conf.template.py", + "templates/seooc_index.template.rst", +]) + +# HTML merge tool +py_binary( + name = "sphinx_html_merge", + srcs = ["src/sphinx_html_merge.py"], + main = "src/sphinx_html_merge.py", + visibility = ["//visibility:public"], +) + +# Sphinx build binary with all required dependencies +py_binary( + name = "score_build", + srcs = ["src/sphinx_wrapper.py"], + data = [], + env = { + "SOURCE_DIRECTORY": "", + "DATA": "", + "ACTION": "check", + }, + main = "src/sphinx_wrapper.py", + visibility = ["//visibility:public"], + deps = [ + "@score_docs_as_code//src:plantuml_for_python", + "@score_docs_as_code//src/extensions/score_sphinx_bundle", + ], +) + +score_module( + name = "score_module", + srcs = glob( + [ + "docs/**/*.rst", + ], + allow_empty = True, + ), + index = "docs/index.rst", + visibility = ["//visibility:public"], + deps = [ + "@score_process//:score_process_module", + ], +) diff --git a/bazel/rules/score_module/docs/conf.py b/bazel/rules/score_module/docs/conf.py deleted file mode 100644 index a393a5b..0000000 --- a/bazel/rules/score_module/docs/conf.py +++ /dev/null @@ -1,52 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2024 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 -# ******************************************************************************* - -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - -project = "SCORE MODULE API" -project_url = "https://eclipse-score.github.io/module_template/" -project_prefix = "MODULE_TEMPLATE_" -author = "S-CORE" -version = "0.1" - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - - -extensions = [ - "sphinx_design", - "sphinx_needs", - "sphinxcontrib.plantuml", - "score_plantuml", - "score_metamodel", - "score_draw_uml_funcs", - "score_source_code_linker", - "score_layout", -] - -exclude_patterns = [ - "bazel-*", - ".venv_docs", -] - -templates_path = ["templates"] - -# Enable numref -numfig = True diff --git a/bazel/rules/score_module/docs/index.rst b/bazel/rules/score_module/docs/index.rst index 3a31284..7096439 100644 --- a/bazel/rules/score_module/docs/index.rst +++ b/bazel/rules/score_module/docs/index.rst @@ -1,7 +1,7 @@ SCORE Module Bazel Rules ========================= -This directory contains Bazel rules for defining and building SCORE safety modules following ISO 26262 SEooC (Safety Element out of Context) standards. +This package provides Bazel build rules for defining and building SCORE documentation modules with integrated Sphinx-based HTML generation. .. contents:: Table of Contents :depth: 2 @@ -11,321 +11,426 @@ This directory contains Bazel rules for defining and building SCORE safety modul Overview -------- -The ``score_module`` package provides Bazel build rules to structure, -validate, and document safety-critical software modules. These rules -integrate with Sphinx documentation generation to produce comprehensive -safety documentation. +The ``score_module`` package provides two complementary Bazel rules for structuring and documenting software modules: -.. uml:: +1. **score_module**: A generic documentation module rule that builds Sphinx-based HTML documentation from RST source files. Suitable for any type of documentation module. - @startuml - [SEooC] as SEooC - [bazel module] as bzlmod - Artifact "Assumptions of Use" as AoU - Artifact "(Assumed) Component Requirements" as CR <> - Artifact "Architecture Design" as AD <> - Artifact "Safety Analysis" as SA <> - Card "Implementation" as Impl <> - Card "Testsuite" as Test <> - - - - bzlmod "1" *-- "*" SEooC : contains - SEooC ..> SEooC : depends on - SEooC "1" *-- "1" AoU : has - SEooC "1" *-- "1" CR : has - SEooC "1" *-- "1" AD : has - SEooC "1" *-- "1" Impl : has - SEooC "1" *-- "1" Test : has - SEooC "1" *-- "1" SA : has - - note right of bzlmod - A score_module can contain - one or more Safety Elements - out of Context (SEooC) - end note +2. **safety_element_out_of_context**: A specialized rule for Safety Elements out of Context (SEooC) that enforces documentation structure with standardized artifacts for assumptions of use, requirements, architecture, and safety analysis. - @enduml +Both rules support **cross-module dependencies** through the ``deps`` attribute, enabling automatic integration of external sphinx-needs references and HTML merging for comprehensive documentation sets. +.. uml:: + @startuml + package "score_module Rules" { + component "score_module" as SM <> + component "safety_element_out_of_context" as SEooC <> + } + + ' score_module structure + artifact "RST Sources" as RST <> + artifact "index.rst" as IDX <> + artifact "conf.py" as CONF <> + + ' SEooC-specific artifacts + artifact "Assumptions of Use" as AoU <> + artifact "Component Requirements" as CR <> + artifact "Architecture Design" as AD <> + artifact "Safety Analysis" as SA <> + + ' Implementation artifacts + card "Implementation" as Impl <> + card "Test Suite" as Test <> + + ' Generated outputs + folder "HTML Output" as HTML { + artifact "index.html" as HTMLIDX + folder "dependencies" as DEPS + } + artifact "needs.json" as NEEDS <> + + ' Dependencies + component "Process Module" as PROC <> + component "Platform Module" as PLAT <> + + ' Relationships for score_module + SM *-- RST : contains + SM *-- IDX : has + SM -- CONF : may have + SM ..> PROC : deps + SM ..> PLAT : deps + SM --> HTML : generates + SM --> NEEDS : exports + + ' Relationships for SEooC + SEooC *-- AoU : requires + SEooC *-- CR : requires + SEooC *-- AD : requires + SEooC *-- SA : requires + SEooC *-- Impl : requires + SEooC *-- Test : requires + SEooC ..> PROC : deps + SEooC ..> PLAT : deps + SEooC --> HTML : generates + SEooC --> NEEDS : exports + SEooC ..> SM : delegates to + + ' Cross-module dependencies + SM ..> SM : can depend on + SEooC ..> SEooC : can depend on + SEooC ..> SM : can depend on + + DEPS -- PROC : merges + DEPS -- PLAT : merges + + note right of SEooC + SEooC automatically generates + index.rst and symlinks artifacts, + then delegates to score_module + for Sphinx build + end note + + note right of DEPS + Dependencies' HTML is merged + into the output, enabling + unified navigation + end note + @enduml Rules and Macros ---------------- -safety_element_out_of_context -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +score_module +~~~~~~~~~~~~ **File:** ``score_module.bzl`` -**Purpose:** Main macro for defining a Safety Element out of Context -(SEooC) module with integrated documentation generation following ISO 26262 -standards. +**Purpose:** Generic rule for building Sphinx-based HTML documentation modules from RST source files with support for dependencies and cross-referencing. **Usage:** .. code-block:: python - safety_element_out_of_context( - name = "my_module", - assumptions_of_use = ":assumptions", - component_requirements = ":requirements", - architectural_design = ":architecture", - safety_analysis = ":safety_analysis", - implementations = [":my_lib", ":my_component"], - tests = [":my_lib_test", ":my_integration_test"], + score_module( + name = "my_documentation", + srcs = glob(["docs/**/*.rst"]), + index = "docs/index.rst", + deps = [ + "@score_process//:score_process_module", + "//other_module:documentation", + ], + sphinx = "//bazel/rules/score_module:score_build", visibility = ["//visibility:public"] ) **Parameters:** -- ``name``: The name of the safety element module. Used as the base name - for all generated targets. -- ``assumptions_of_use``: Label to a ``.rst`` or ``.md`` file containing the - Assumptions of Use, which define the safety-relevant operating conditions - and constraints for the SEooC as required by ISO 26262-10 clause 5.4.4. -- ``component_requirements``: Label to a ``.rst`` or ``.md`` file containing - the component requirements specification, defining functional and safety - requirements as required by ISO 26262-3 clause 7. -- ``architectural_design``: Label to a ``.rst`` or ``.md`` file containing - the architectural design specification, describing the software architecture - and design decisions as required by ISO 26262-6 clause 7. -- ``safety_analysis``: Label to a ``.rst`` or ``.md`` file containing the - safety analysis, including FMEA, FMEDA, FTA, or other safety analysis - results as required by ISO 26262-9 clause 8. Documents hazard analysis and - safety measures. -- ``implementations``: List of labels to Bazel targets representing the actual - software implementation (cc_library, cc_binary, etc.) that realizes the - component requirements. This is the source code that implements the safety - functions as required by ISO 26262-6 clause 8. -- ``tests``: List of labels to Bazel test targets (cc_test, py_test, etc.) - that verify the implementation against requirements. Includes unit tests and - integration tests as required by ISO 26262-6 clause 9 for software unit - verification. -- ``visibility``: Bazel visibility specification for the generated SEooC - target. Controls which other packages can depend on this safety element. +- ``name``: The name of the documentation module +- ``srcs``: List of RST source files for the documentation +- ``index``: Path to the main index.rst file +- ``deps``: Optional list of other ``score_module`` or ``safety_element_out_of_context`` targets that this module depends on. Dependencies are automatically integrated for cross-referencing via sphinx-needs and their HTML is merged into the output. +- ``sphinx``: Label to the Sphinx build binary (default: ``//bazel/rules/score_module:score_build``) +- ``config``: Optional custom conf.py file. If not provided, a default configuration is generated. +- ``visibility``: Bazel visibility specification **Generated Targets:** -This macro creates multiple targets automatically: +- ````: Main target producing the HTML documentation directory +- ``_needs``: Internal target generating the sphinx-needs JSON file for cross-referencing -1. ``_index``: Generates index.rst and conf.py files for the module - documentation -2. ``_seooc_index_lib``: Sphinx documentation library for the - generated index -3. ````: The main SEooC target that aggregates all documentation -4. ``.html``: Convenience target to build HTML documentation +**Output:** -**Implementation Details:** +- ``/html``: Directory containing the built HTML documentation with integrated dependencies +- ``/needs.json``: Sphinx-needs JSON file for external cross-references -The macro orchestrates several internal rules to: +**Build Strategy** -- Generate a documentation index with a structured table of contents -- Organize documentation files under ``docs/safety_elements//`` -- Integrate assumptions of use and component requirements into a unified documentation structure -- Provide Sphinx-compatible output for HTML generation +The ``score_module`` rule implements a multi-phase build strategy to ensure proper dependency resolution and documentation integration: -Private Rules -------------- +**Phase 1: Generate Needs JSON** -seooc -~~~~~ +First, the rule builds a ``needs.json`` file for the current module by running Sphinx in a preliminary pass. This JSON file contains all sphinx-needs definitions (requirements, architecture elements, test cases, etc.) from the module's documentation. The needs.json is generated using the ``score_needs`` internal rule. -**File:** ``private/seooc.bzl`` +**Phase 2: Build Dependent Modules** -**Purpose:** Internal rule that aggregates safety documentation artifacts -into a Sphinx-compatible structure. +Before building the main module's HTML, Bazel ensures all modules listed in the ``deps`` attribute are built first. This gives us: -**Implementation:** ``_seooc_build_impl`` +- The ``needs.json`` files from all dependencies for external cross-referencing +- The complete HTML documentation trees from all dependencies for merging -**Functionality:** +This phase leverages Bazel's dependency graph to parallelize builds where possible. -- Collects documentation from ``assumptions_of_use`` and - ``component_requirements`` dependencies -- Reorganizes file paths to place artifacts under - ``docs/safety_elements//`` -- Merges the generated index with artifact documentation -- Returns a ``SphinxDocsLibraryInfo`` provider for downstream consumption +**Phase 3: Generate Main Module HTML** -**Attributes:** +With all dependency needs.json files available, Sphinx builds the main module's HTML documentation. During this phase: -- ``assumptions_of_use``: Label to assumptions of use documentation (mandatory) -- ``component_requirements``: Label to component requirements documentation (mandatory) -- ``index``: Label to the generated index file (mandatory) +- The ``needs_external_needs`` configuration is automatically populated with paths to all dependency needs.json files +- Sphinx resolves ``:need:`` references across module boundaries +- HTML pages are generated in a temporary ``_html`` directory -**Output:** +**Phase 4: Merge HTML Documentation** -Returns a ``SphinxDocsLibraryInfo`` provider containing: +Finally, the ``sphinx_html_merge`` tool combines the documentation: -- ``transitive``: Depset of documentation file structs with relocated paths -- ``files``: Empty list (files are in transitive dependencies) +1. Copies the main module's HTML from ``_html/`` to the final ``html/`` output directory +2. For each dependency, copies its ``html/`` directory into the output as a subdirectory +3. Preserves the module hierarchy, enabling navigation between related documentation -seooc_sphinx_environment -~~~~~~~~~~~~~~~~~~~~~~~~ +The result is a unified documentation tree where users can seamlessly navigate from the main module to any of its dependencies. -**File:** ``private/seooc_index.bzl`` +**Build Artifacts** -**Purpose:** Generates the Sphinx environment files (index.rst and conf.py) -for a safety module. +Each successful build produces: -**Implementation:** ``_seooc_sphinx_environment_impl`` +- ``/html/``: Complete merged HTML documentation +- ``/needs.json``: Sphinx-needs export for this module +- ``/_html/``: Intermediate HTML (before merging) -**Functionality:** -- Creates a module-specific ``index.rst`` with: +safety_element_out_of_context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Module name as header (uppercase with underline) - - Table of contents (toctree) linking to all safety artifacts - - References to assumptions of use and component requirements index files +**File:** ``score_module.bzl`` -- Generates a ``conf.py`` configuration file (currently placeholder content) +**Purpose:** Specialized macro for defining a Safety Element out of Context (SEooC) module documentation structure and automatic index generation. -**Attributes:** +**Usage:** -- ``module_name``: String name of the module (used for header generation) -- ``assumptions_of_use``: Label to assumptions documentation -- ``component_requirements``: Label to requirements documentation +.. code-block:: python -**Generated Content Example:** + safety_element_out_of_context( + name = "my_seooc", + assumptions_of_use = ["docs/assumptions_of_use.rst"], + component_requirements = ["docs/requirements.rst"], + architectural_design = ["docs/architecture.rst"], + safety_analysis = ["docs/safety_analysis.rst"], + deps = [ + "@score_platform//:score_platform_module", + "@score_process//:score_process_module", + ], + implementations = [":my_lib"], + tests = [":my_lib_test"], + visibility = ["//visibility:public"] + ) -.. code-block:: rst +**Parameters:** - MY_MODULE - ========= +- ``name``: The name of the safety element module +- ``assumptions_of_use``: List of labels to ``.rst`` or ``.md`` files containing Assumptions of Use documentation +- ``component_requirements``: List of labels to ``.rst`` or ``.md`` files containing component requirements specification +- ``architectural_design``: List of labels to ``.rst`` or ``.md`` files containing architectural design specification +- ``safety_analysis``: List of labels to ``.rst`` or ``.md`` files containing safety analysis documentation (FMEA, DFA, etc.) +- ``deps``: Optional list of other ``score_module`` or ``safety_element_out_of_context`` targets that this SEooC depends on. Dependencies enable cross-referencing between modules and merge their HTML documentation into the final output. +- ``implementations``: List of labels to implementation targets (cc_library, cc_binary, etc.) that realize the component requirements +- ``tests``: List of labels to test targets (cc_test, py_test, etc.) that verify the implementation against requirements +- ``sphinx``: Label to the Sphinx build binary (default: ``//bazel/rules/score_module:score_build``) +- ``visibility``: Bazel visibility specification - .. toctree:: - :maxdepth: 2 - :caption: Contents: +**Generated Targets:** - assumptions_of_use/index - component_requirements/index - architectural_design/index - safety_analysis/index +- ``_seooc_index``: Internal target that generates index.rst and symlinks all artifact files +- ````: Main SEooC target (internally calls ``score_module``) producing HTML documentation +- ``_needs``: Sphinx-needs JSON file for cross-referencing -**Output:** +**Implementation Details:** + +The macro automatically: + +- Generates an index.rst file with a toctree referencing all provided artifacts +- Creates symlinks to artifact files (assumptions of use, requirements, architecture, safety analysis) for co-location with the generated index +- Delegates to ``score_module`` for actual Sphinx build and HTML generation +- Integrates dependencies for cross-module referencing and HTML merging + +Dependency Management +--------------------- + +Both ``score_module`` and ``safety_element_out_of_context`` support cross-module dependencies through the ``deps`` attribute. This enables: + +**Cross-Referencing with Sphinx-Needs** + +Dependencies are automatically configured for sphinx-needs external references, allowing documents to reference requirements, architecture elements, and other needs across module boundaries using the ``:need:`` role. + +**HTML Documentation Merging** + +When building a module with dependencies, the HTML output from all dependent modules is merged into a unified documentation tree. For example: + +.. code-block:: text + + /html/ + ├── index.html # Main module documentation + ├── _static/ # Sphinx static assets + ├── dependency_module_1/ # Merged from first dependency + │ └── index.html + └── dependency_module_2/ # Merged from second dependency + └── index.html + +This allows seamless navigation between related documentation modules while maintaining independent build targets. -Returns ``DefaultInfo`` with generated ``index.rst`` files. +**Example with Dependencies:** + +.. code-block:: python + + # Process module (external dependency) + # @score_process//:score_process_module + + # Platform module (external dependency) + # @score_platform//:score_platform_module + + # My component that depends on process and platform + safety_element_out_of_context( + name = "my_component_seooc", + assumptions_of_use = ["docs/assumptions.rst"], + component_requirements = ["docs/requirements.rst"], + architectural_design = ["docs/architecture.rst"], + safety_analysis = ["docs/safety.rst"], + deps = [ + "@score_process//:score_process_module", + "@score_platform//:score_platform_module", + ], + ) Documentation Structure ----------------------- -When using these rules, documentation is organized in the Bazel sandbox as -follows:: +**For safety_element_out_of_context:** + +The macro automatically generates an index.rst and organizes files:: + + bazel-bin/_seooc_index/ + ├── index.rst # Generated toctree + ├── assumptions_of_use.rst # Symlinked artifact + ├── component_requirements.rst # Symlinked artifact + ├── architectural_design.rst # Symlinked artifact + └── safety_analysis.rst # Symlinked artifact + +**For score_module:** + +User provides the complete source structure:: docs/ - └── safety_elements/ - └── / - ├── index.rst (generated) - ├── conf.py (generated) - ├── assumptions_of_use/ - │ └── (user-provided documentation) - ├── component_requirements/ - │ └── (user-provided documentation) - ├── architectural_design/ - │ └── (user-provided documentation) - └── safety_analysis/ - └── (user-provided documentation) - - bazel-bin/ - └── / - └── _sources/ - └── (generated documentation sources) - -This structure reflects the file organization created in the Bazel sandbox -during the documentation generation process. The generated ``index.rst`` file -includes a table of contents that references all provided artifacts. + ├── index.rst # User-provided + ├── section1.rst + ├── section2.rst + └── subsection/ + └── details.rst + +**Output Structure:** + +Both rules produce a standardized output:: + + bazel-bin// + ├── html/ # Built HTML documentation + │ ├── index.html + │ ├── _static/ + │ ├── / # Merged dependency HTML + │ └── / # Merged dependency HTML + └── needs.json # Sphinx-needs export Integration with Sphinx ------------------------ -The rules generate ``SphinxDocsLibraryInfo`` providers that are compatible -with ``@rules_python//sphinxdocs``. This enables: +The rules provide first-class Sphinx integration: + +**Sphinx-Needs Support** + +- Automatic configuration of external needs references from dependencies +- Export of needs.json for downstream consumers +- Cross-module traceability using ``:need:`` references + +**Sphinx Extensions** -- Automatic discovery of documentation files by Sphinx -- Proper path relocation for modular documentation -- Transitive dependency handling across multiple safety modules -- HTML, PDF, and other Sphinx output formats +The default configuration includes common SCORE extensions: -Usage Example -------------- +- sphinx-needs: Requirements management and traceability +- sphinx_design: Modern UI components +- myst_parser: Markdown support alongside RST -Complete example in a BUILD file: +**Custom Configuration** + +For ``score_module``, provide a custom conf.py via the ``config`` attribute to override the default Sphinx configuration. + + +Usage Examples +-------------- + +Example 1: Generic Documentation Module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python - load("@baselibs//bazel/score_module:score_module.bzl", - "safety_element_out_of_context") + load("//bazel/rules/score_module:score_module.bzl", "score_module") - # Documentation artifacts - sphinx_docs_library( - name = "assumptions", - srcs = ["docs/assumptions_of_use.rst"], + score_module( + name = "platform_docs", + srcs = glob(["docs/**/*.rst"]), + index = "docs/index.rst", + deps = [ + "@score_process//:score_process_module", + ], ) - sphinx_docs_library( - name = "requirements", - srcs = ["docs/component_requirements.rst"], - ) +Build and view: - sphinx_docs_library( - name = "architecture", - srcs = ["docs/architectural_design.rst"], - ) +.. code-block:: bash - sphinx_docs_library( - name = "safety", - srcs = ["docs/safety_analysis.rst"], - ) + bazel build //:platform_docs + # Output: bazel-bin/platform_docs/html/ - # Implementation targets +Example 2: Safety Element out of Context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + load("//bazel/rules/score_module:score_module.bzl", + "safety_element_out_of_context") + + # Implementation cc_library( - name = "lifecycle_lib", - srcs = ["lifecycle_manager.cpp"], - hdrs = ["lifecycle_manager.h"], + name = "kvs_lib", + srcs = ["kvs.cpp"], + hdrs = ["kvs.h"], ) - # Test targets + # Tests cc_test( - name = "lifecycle_test", - srcs = ["lifecycle_manager_test.cpp"], - deps = [":lifecycle_lib"], + name = "kvs_test", + srcs = ["kvs_test.cpp"], + deps = [":kvs_lib"], ) - # Safety Element out of Context + # SEooC with dependencies safety_element_out_of_context( - name = "lifecycle_manager_seooc", - assumptions_of_use = ":assumptions", - component_requirements = ":requirements", - architectural_design = ":architecture", - safety_analysis = ":safety", - implementations = [":lifecycle_lib"], - tests = [":lifecycle_test"], - visibility = ["//visibility:public"], + name = "kvs_seooc", + assumptions_of_use = ["docs/assumptions.rst"], + component_requirements = ["docs/requirements.rst"], + architectural_design = ["docs/architecture.rst"], + safety_analysis = ["docs/fmea.rst", "docs/dfa.rst"], + deps = [ + "@score_platform//:score_platform_module", + "@score_process//:score_process_module", + ], + implementations = [":kvs_lib"], + tests = [":kvs_test"], ) -Then build the documentation: +Build and view: .. code-block:: bash - # Build the SEooC target - bazel build //:lifecycle_manager_seooc - - -Dependencies ------------- - -- ``@rules_python//sphinxdocs``: Sphinx documentation build rules -- ``SphinxDocsLibraryInfo``: Provider for documentation artifacts + bazel build //:kvs_seooc + # Output: bazel-bin/kvs_seooc/html/ + # Includes merged HTML from score_platform and score_process modules Design Rationale ---------------- -These rules enforce a structured approach to safety documentation by: +These rules provide a structured approach to documentation by: -1. **Standardization**: All safety modules follow the same documentation - structure -2. **Traceability**: Build system ensures all required artifacts are present -3. **Modularity**: Documentation can be composed from multiple sources -4. **Automation**: Index generation and path management are automated -5. **Integration**: Seamless integration with existing Sphinx workflows +1. **Two-Tier Architecture**: Generic ``score_module`` for flexibility, specialized ``safety_element_out_of_context`` for safety-critical work +2. **Dependency Management**: Automatic cross-referencing and HTML merging across modules +3. **Standardization**: SEooC enforces consistent structure for safety documentation +4. **Traceability**: Sphinx-needs integration enables bidirectional traceability +5. **Automation**: Index generation, symlinking, and configuration management are automatic +6. **Build System Integration**: Bazel ensures reproducible, cacheable documentation builds diff --git a/bazel/rules/score_module/private/score_module.bzl b/bazel/rules/score_module/private/score_module.bzl new file mode 100644 index 0000000..8865098 --- /dev/null +++ b/bazel/rules/score_module/private/score_module.bzl @@ -0,0 +1,299 @@ +# ====================================================================================== +# Providers +# ====================================================================================== + +ScoreModuleInfo = provider( + doc = "Provider for Sphinx HTML module documentation", + fields = { + "html_dir": "Directory containing HTML files", + }, +) + +ScoreNeedsInfo = provider( + doc = "Provider for sphinx-needs info", + fields = { + "needs_json_file": "Direct needs.json file for this module", + "needs_json_files": "Depset of needs.json files including transitive dependencies", + }, +) + +# ====================================================================================== +# Helpers +# ====================================================================================== +def _create_config_py(ctx): + """Get or generate the conf.py configuration file. + + Args: + ctx: Rule context + """ + if ctx.attr.config: + config_file = ctx.attr.config.files.to_list()[0] + else: + config_file = ctx.actions.declare_file(ctx.label.name + "/conf.py") + template = ctx.file._config_template + + # Read template and substitute PROJECT_NAME + ctx.actions.expand_template( + template = template, + output = config_file, + substitutions = { + "{PROJECT_NAME}": ctx.label.name.replace("_", " ").title(), + }, + ) + return config_file + +# ====================================================================================== +# Common attributes for Sphinx rules +# ====================================================================================== +sphinx_rule_attrs = { + "srcs": attr.label_list( + allow_files = True, + doc = "List of source files for the Sphinx documentation.", + ), + "sphinx": attr.label( + doc = "The Sphinx build binary to use.", + mandatory = True, + executable = True, + cfg = "exec", + ), + "config": attr.label( + allow_files = [".py"], + doc = "Configuration file (conf.py) for the Sphinx documentation. If not provided, a default config will be generated.", + mandatory = False, + ), + "index": attr.label( + allow_files = [".rst"], + doc = "Index file (index.rst) for the Sphinx documentation.", + mandatory = True, + ), + "deps": attr.label_list( + doc = "List of other score_module targets this module depends on for intersphinx.", + ), + "_config_template": attr.label( + default = Label("//bazel/rules/score_module:templates/conf.template.py"), + allow_single_file = True, + doc = "Template for generating default conf.py", + ), + "_html_merge_tool": attr.label( + default = Label("//bazel/rules/score_module:sphinx_html_merge"), + executable = True, + cfg = "exec", + doc = "Tool for merging HTML directories", + ), +} + +# ====================================================================================== +# Rule implementations +# ====================================================================================== +def _score_needs_impl(ctx): + output_path = ctx.label.name.replace("_needs", "") + "/needs.json" + needs_output = ctx.actions.declare_file(output_path) + + # Get config file (generate or use provided) + config_file = _create_config_py(ctx) + + # Phase 1: Build needs.json (without external needs) + needs_inputs = ctx.files.srcs + [config_file] + + if ctx.attr.config: + needs_inputs = needs_inputs + ctx.files.config + + needs_args = [ + "--index_file", + ctx.attr.index.files.to_list()[0].path, + "--output_dir", + needs_output.dirname, + "--config", + config_file.path, + "--builder", + "needs", + ] + + ctx.actions.run( + inputs = needs_inputs, + outputs = [needs_output], + arguments = needs_args, + progress_message = "Generating needs.json for: %s" % ctx.label.name, + executable = ctx.executable.sphinx, + ) + + transitive_needs = [dep[ScoreNeedsInfo].needs_json_files for dep in ctx.attr.deps if ScoreNeedsInfo in dep] + needs_json_files = depset([needs_output], transitive = transitive_needs) + + return [ + DefaultInfo( + files = needs_json_files, + ), + ScoreNeedsInfo( + needs_json_file = needs_output, # Direct file only + needs_json_files = needs_json_files, # Transitive depset + ), + ] + +def _score_html_impl(ctx): + """Implementation for building a Sphinx module with two-phase build. + + Phase 1: Generate needs.json for this module and collect from all deps + Phase 2: Generate HTML with external needs and merge all dependency HTML + """ + + # Collect all transitive dependencies with deduplication + modules = [] + + needs_external_needs = {} + for dep in ctx.attr.needs: + if ScoreNeedsInfo in dep: + dep_name = dep.label.name.replace("_needs", "") + needs_external_needs[dep.label.name] = { + "base_url": dep_name, # Relative path to the subdirectory where dep HTML is copied + "json_path": dep[ScoreNeedsInfo].needs_json_file.path, # Use direct file + "id_prefix": "", + "css_class": "", + } + + for dep in ctx.attr.deps: + if ScoreModuleInfo in dep: + modules.extend([dep[ScoreModuleInfo].html_dir]) + + needs_external_needs_json = ctx.actions.declare_file(ctx.label.name + "/needs_external_needs.json") + + ctx.actions.write( + output = needs_external_needs_json, + content = json.encode_indent(needs_external_needs, indent = " "), + ) + + # Read template and substitute PROJECT_NAME + config_file = ctx.actions.declare_file(ctx.label.name + "/conf.py") + template = ctx.file._config_template + + ctx.actions.expand_template( + template = template, + output = config_file, + substitutions = { + "{PROJECT_NAME}": ctx.label.name.replace("_", " ").title(), + }, + ) + + # Build HTML with external needs + html_inputs = ctx.files.srcs + ctx.files.needs + [config_file, needs_external_needs_json] + sphinx_html_output = ctx.actions.declare_directory(ctx.label.name + "/_html") + html_args = [ + "--index_file", + ctx.attr.index.files.to_list()[0].path, + "--output_dir", + sphinx_html_output.path, + "--config", + config_file.path, + "--builder", + "html", + ] + + ctx.actions.run( + inputs = html_inputs, + outputs = [sphinx_html_output], + arguments = html_args, + progress_message = "Building HTML with external needs: %s" % ctx.label.name, + executable = ctx.executable.sphinx, + ) + + # Create final HTML output directory with dependencies using Python merge script + html_output = ctx.actions.declare_directory(ctx.label.name + "/html") + + # Build arguments for the merge script + merge_args = [ + "--output", + html_output.path, + "--main", + sphinx_html_output.path, + ] + + merge_inputs = [sphinx_html_output] + + # Add each dependency + for dep in ctx.attr.deps: + if ScoreModuleInfo in dep: + dep_html_dir = dep[ScoreModuleInfo].html_dir + dep_name = dep.label.name + merge_inputs.append(dep_html_dir) + merge_args.extend(["--dep", dep_name + ":" + dep_html_dir.path]) + + # Merging html files + ctx.actions.run( + inputs = merge_inputs, + outputs = [html_output], + arguments = merge_args, + progress_message = "Merging HTML with dependencies for %s" % ctx.label.name, + executable = ctx.executable._html_merge_tool, + ) + + return [ + DefaultInfo(files = depset(ctx.files.needs + [html_output])), + ScoreModuleInfo( + html_dir = html_output, + ), + ] + +# ====================================================================================== +# Rule definitions +# ====================================================================================== + +_score_needs = rule( + implementation = _score_needs_impl, + attrs = sphinx_rule_attrs, +) + +_score_html = rule( + implementation = _score_html_impl, + attrs = dict(sphinx_rule_attrs, needs = attr.label_list( + allow_files = True, + doc = "Submodule symbols.needs targets for this module.", + )), +) + +# ====================================================================================== +# Rule wrappers +# ====================================================================================== + +def score_module( + name, + srcs, + index, + config = None, + deps = [], + sphinx = "@//bazel/rules/score_module:score_build", + visibility = ["//visibility:public"]): + """Build a Sphinx module with transitive HTML dependencies. + + This rule builds documentation modules into complete HTML sites with + transitive dependency collection. All dependencies are automatically + included in a modules/ subdirectory for intersphinx cross-referencing. + + Args: + name: Name of the target + srcs: List of source files (.rst, .md) with index file first + index: Label to index.rst file + config: Label to conf.py configuration file (optional, will be auto-generated if not provided) + deps: List of other score_module targets this module depends on + sphinx: Label to sphinx build binary (default: :sphinx_build) + visibility: Bazel visibility + """ + _score_needs( + name = name + "_needs", + srcs = srcs, + config = config, + index = index, + deps = [d + "_needs" for d in deps], + sphinx = sphinx, + visibility = visibility, + ) + + _score_html( + name = name, + srcs = srcs, + config = config, + index = index, + deps = deps, + needs = [d + "_needs" for d in deps], + sphinx = sphinx, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/private/score_seooc.bzl b/bazel/rules/score_module/private/score_seooc.bzl new file mode 100644 index 0000000..6408c72 --- /dev/null +++ b/bazel/rules/score_module/private/score_seooc.bzl @@ -0,0 +1,237 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Safety Element out of Context (SEooC) build rules for S-CORE projects. + +This module provides macros and rules for building SEooC documentation modules +following S-CORE process guidelines. A SEooC is a safety-related element developed +independently of a specific vehicle project. +""" + +load("//bazel/rules/score_module/private:score_module.bzl", "score_module") + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _generate_seooc_index_impl(ctx): + """Generate index.rst file with references to all SEooC artifacts. + + This rule creates a Sphinx index.rst file that includes references to all + the SEooC documentation artifacts (assumptions of use, requirements, design, + and safety analysis). + + Args: + ctx: Rule context + + Returns: + DefaultInfo provider with generated index.rst file + """ + + # Declare output index.rst file + index_rst = ctx.actions.declare_file(ctx.label.name + "/index.rst") + + # Collect all artifact files and create symlinks + output_files = [index_rst] + artifacts_by_type = { + "assumptions_of_use": [], + "component_requirements": [], + "architectural_design": [], + "safety_analysis": [], + } + + # Process each artifact type + for artifact_name in ["assumptions_of_use", "component_requirements", "architectural_design", "safety_analysis"]: + attr_list = getattr(ctx.attr, artifact_name) + if attr_list: + # For label_list attributes, iterate over each label + for label in attr_list: + files = label.files.to_list() + for artifact_file in files: + # Check that artifact is not named index.rst + if artifact_file.basename == "index.rst": + fail("Error in {}: Artifact file '{}' in '{}' cannot be named 'index.rst' as this file is generated by the SEooC rule and would be overwritten.".format( + ctx.label, + artifact_file.path, + artifact_name, + )) + + # Create symlink in same directory as index + output_file = ctx.actions.declare_file( + ctx.label.name + "/" + artifact_file.basename, + ) + output_files.append(output_file) + + # Symlink instead of copying for better performance + ctx.actions.symlink( + output = output_file, + target_file = artifact_file, + ) + + # Add reference to index (without file extension) + doc_ref = artifact_file.basename.replace(".rst", "").replace(".md", "") + artifacts_by_type[artifact_name].append(doc_ref) + + # Substitute template variables (template handles indentation) + title = ctx.attr.module_name + underline = "=" * len(title) + + ctx.actions.expand_template( + template = ctx.file.template, + output = index_rst, + substitutions = { + "{title}": title, + "{underline}": underline, + "{description}": ctx.attr.description, + "{assumptions_of_use}": "\n ".join(artifacts_by_type["assumptions_of_use"]), + "{component_requirements}": "\n ".join(artifacts_by_type["component_requirements"]), + "{architectural_design}": "\n ".join(artifacts_by_type["architectural_design"]), + "{safety_analysis}": "\n ".join(artifacts_by_type["safety_analysis"]), + }, + ) + + return [ + DefaultInfo(files = depset(output_files)), + ] + +# ============================================================================ +# Private Rule Definition +# ============================================================================ + +_generate_seooc_index = rule( + implementation = _generate_seooc_index_impl, + doc = "Generates index.rst file with references to SEooC artifacts", + attrs = { + "module_name": attr.string( + mandatory = True, + doc = "Name of the SEooC module (used as document title)", + ), + "description": attr.string( + mandatory = True, + doc = "Description of the SEooC component that appears at the beginning of the documentation. Supports RST formatting.", + ), + "template": attr.label( + allow_single_file = [".rst"], + mandatory = True, + doc = "Template file for generating index.rst", + ), + "assumptions_of_use": attr.label_list( + allow_files = [".rst", ".md"], + mandatory = True, + doc = "Assumptions of Use document as defined in the S-CORE process", + ), + "component_requirements": attr.label_list( + allow_files = [".rst", ".md"], + mandatory = True, + doc = "Component requirements specification as defined in the S-CORE process", + ), + "architectural_design": attr.label_list( + allow_files = [".rst", ".md"], + mandatory = True, + doc = "Architectural design specification as defined in the S-CORE process", + ), + "safety_analysis": attr.label_list( + allow_files = [".rst", ".md"], + mandatory = True, + doc = "Safety analysis documentation as defined in the S-CORE process", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def safety_element_out_of_context( + name, + assumptions_of_use, + component_requirements, + architectural_design, + safety_analysis, + description, + implementations = [], + tests = [], + deps = [], + sphinx = "//bazel/rules/score_module:score_build", + visibility = None): + """Define a Safety Element out of Context (SEooC) following S-CORE process guidelines. + + This macro creates a complete SEooC module with integrated documentation + generation. It generates an index.rst file referencing all SEooC artifacts + and builds HTML documentation using the score_module infrastructure. + + A SEooC is a safety-related architectural element (e.g., a software component) + that is developed independently of a specific vehicle project and can be + integrated into different vehicle platforms. + + Args: + name: The name of the safety element module. Used as the base name for + all generated targets. + assumptions_of_use: Label to a .rst or .md file containing the Assumptions + of Use, which define the safety-relevant operating conditions and + constraints for the SEooC as defined in the S-CORE process. + component_requirements: Label to a .rst or .md file containing the + component requirements specification, defining functional and safety + requirements as defined in the S-CORE process. + architectural_design: Label to a .rst or .md file containing the + architectural design specification, describing the software + architecture and design decisions as defined in the S-CORE process. + safety_analysis: Label to a .rst or .md file containing the safety + analysis, including FMEA, FMEDA, FTA, or other safety analysis + results as defined in the S-CORE process. + description: String containing a high-level description of the SEooC + component. This text appears at the beginning of the generated documentation, + providing context about what the component does and its purpose. + Supports RST formatting. + implementations: Optional list of labels to Bazel targets representing + the actual software implementation (cc_library, cc_binary, etc.) + that realizes the component requirements. This is the source code + that implements the safety functions as defined in the S-CORE process. + tests: Optional list of labels to Bazel test targets (cc_test, py_test, etc.) + that verify the implementation against requirements. Includes unit + tests and integration tests as defined in the S-CORE process. + deps: Optional list of other score_module or SEooC targets this module + depends on. Cross-references will work automatically. + sphinx: Label to sphinx build binary. Default: //bazel/rules/score_module:score_build + visibility: Bazel visibility specification for the generated SEooC targets. + + Generated Targets: + _seooc_index: Internal rule that generates index.rst and copies artifacts + : Main SEooC target (score_module) with HTML documentation + _needs: Internal target for sphinx-needs JSON generation + """ + + # Step 1: Generate index.rst and collect all artifacts + _generate_seooc_index( + name = name + "_seooc_index", + module_name = name, + description = description, + template = "//bazel/rules/score_module:templates/seooc_index.template.rst", + assumptions_of_use = assumptions_of_use, + component_requirements = component_requirements, + architectural_design = architectural_design, + safety_analysis = safety_analysis, + visibility = ["//visibility:private"], + ) + + # Step 2: Create score_module using generated index and artifacts + # The index file is part of the _seooc_index target outputs + score_module( + name = name, + srcs = [":" + name + "_seooc_index"], + index = ":" + name + "_seooc_index", # Label to the target, not a path + deps = deps, + sphinx = sphinx, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/private/seooc.bzl b/bazel/rules/score_module/private/seooc.bzl index d0bfbe6..e42e917 100644 --- a/bazel/rules/score_module/private/seooc.bzl +++ b/bazel/rules/score_module/private/seooc.bzl @@ -1,82 +1,12 @@ -load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo") +""" +Backwards compatibility wrapper for safety_element_out_of_context macro. -seooc_artifacts = { - "assumptions_of_use": attr.label( - providers = [SphinxDocsLibraryInfo], - mandatory = True, - doc = "Label to a sphinx_docs_library target containing the Assumptions of Use, which define the safety-relevant operating conditions and constraints for the SEooC as required by ISO 26262-10 clause 5.4.4.", - ), - "component_requirements": attr.label( - providers = [SphinxDocsLibraryInfo], - mandatory = True, - doc = "Label to a sphinx_docs_library target containing the component requirements specification, defining functional and safety requirements as required by ISO 26262-3 clause 7.", - ), - "architectural_design": attr.label( - providers = [SphinxDocsLibraryInfo], - mandatory = True, - doc = "Label to a sphinx_docs_library target containing the architectural design specification, describing the software architecture and design decisions as required by ISO 26262-6 clause 7.", - ), - "safety_analysis": attr.label( - providers = [SphinxDocsLibraryInfo], - mandatory = True, - doc = "Label to a sphinx_docs_library target containing the safety analysis, including FMEA, FMEDA, FTA, or other safety analysis results as required by ISO 26262-9 clause 8. Documents hazard analysis and safety measures.", - ), -} +This file re-exports the macro from its new location in score_seooc.bzl. +It exists for backwards compatibility and can be removed once all references +are updated to use score_seooc.bzl directly. +""" -seooc_targets = { - "implementations": attr.label( - mandatory = False, - doc = "", - ), - "tests": attr.label( - mandatory = False, - doc = "", - ), -} +load("//bazel/rules/score_module/private:score_seooc.bzl", _safety_element_out_of_context = "safety_element_out_of_context") -def _seooc_build_impl(ctx): - """Implementation of safety_element build rule for ISO 26262 SEooC.""" - - all_files = [] - for artifact in seooc_artifacts: - dep = getattr(ctx.attr, artifact) - for t in dep[SphinxDocsLibraryInfo].transitive.to_list(): - entry = struct( - strip_prefix = t.strip_prefix, - prefix = "docs/safety_elements/" + ctx.attr.name + "/" + t.prefix, - files = t.files, - ) - all_files.append(entry) - - index = ctx.attr.index - for t in index[SphinxDocsLibraryInfo].transitive.to_list(): - entry = struct( - strip_prefix = t.strip_prefix, - prefix = "", - files = t.files, - ) - all_files.append(entry) - - result = depset(all_files) - return [ - DefaultInfo( - files = depset([]), - ), - SphinxDocsLibraryInfo( - strip_prefix = "", - prefix = "", - files = [], - transitive = result, - ), - ] - -seooc = rule( - implementation = _seooc_build_impl, - attrs = seooc_artifacts | { - "index": attr.label( - allow_files = [".rst", ".md", ".py"], - mandatory = True, - doc = "", - ), - }, -) +# Re-export the macro for backwards compatibility +safety_element_out_of_context = _safety_element_out_of_context diff --git a/bazel/rules/score_module/private/seooc_sphinx_environment.bzl b/bazel/rules/score_module/private/seooc_sphinx_environment.bzl deleted file mode 100644 index 9d1738b..0000000 --- a/bazel/rules/score_module/private/seooc_sphinx_environment.bzl +++ /dev/null @@ -1,54 +0,0 @@ -load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo") -load("//bazel/rules/score_module/private:seooc.bzl", "seooc_artifacts") - -index_content = """ - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - -""" - -def _seooc_sphinx_environment_impl(ctx): - """Generate the index.rst file for a seooc""" - - index_rst = ctx.actions.declare_file("docs/safety_elements/" + ctx.attr.module_name + "/index.rst") - - header = ctx.attr.module_name.upper() - header += "\n" + "=" * len(header) - - file_content = header + index_content - - for artifact in seooc_artifacts: - attr = getattr(ctx.attr, artifact) - if attr: - # Get all files from the SphinxDocsLibraryInfo - src_files = list(attr[SphinxDocsLibraryInfo].files) - if src_files: - # Use the first file (typically the main documentation file) - artifact_index_file = src_files[0] - - # Create link path from the file - link = artifact_index_file.short_path.replace(".rst", "").replace(".md", "") - if ctx.label.package: - print("replacing link: " + ctx.label.package + "/") - link = link.replace(ctx.label.package + "/", "") - file_content += " " + link + "\n" - - ctx.actions.write( - output = index_rst, - content = file_content, - ) - - return ( - DefaultInfo( - files = depset([index_rst]), - ) - ) - -seooc_sphinx_environment = rule( - implementation = _seooc_sphinx_environment_impl, - attrs = seooc_artifacts | { - "module_name": attr.string(), - }, -) diff --git a/bazel/rules/score_module/score_module.bzl b/bazel/rules/score_module/score_module.bzl index 6ed760a..df3d521 100644 --- a/bazel/rules/score_module/score_module.bzl +++ b/bazel/rules/score_module/score_module.bzl @@ -1,80 +1,13 @@ load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_docs") load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library") -load("//bazel/rules/score_module/private:seooc.bzl", "seooc") -load("//bazel/rules/score_module/private:seooc_sphinx_environment.bzl", "seooc_sphinx_environment") - -def safety_element_out_of_context( - name, - assumptions_of_use, - component_requirements, - architectural_design, - safety_analysis, - implementations, - tests, - visibility): - """Defines a Safety Element out of Context (SEooC) following ISO 26262 standards. - - This macro creates a complete SEooC module with integrated documentation generation - using Sphinx. It packages all required ISO 26262 artifacts and generates HTML - documentation for safety certification. - - Args: - name: The name of the safety element module. Used as the base name for all - generated targets. - assumptions_of_use: Label to a .rst or .md file containing the Assumptions of Use, - which define the safety-relevant operating conditions and constraints for the - SEooC as required by ISO 26262-10 clause 5.4.4. - component_requirements: Label to a .rst or .md file containing the component - requirements specification, defining functional and safety requirements as - required by ISO 26262-3 clause 7. - architectural_design: Label to a .rst or .md file containing the architectural - design specification, describing the software architecture and design decisions - as required by ISO 26262-6 clause 7. - safety_analysis: Label to a .rst or .md file containing the safety analysis, - including FMEA, FMEDA, FTA, or other safety analysis results as required by - ISO 26262-9 clause 8. Documents hazard analysis and safety measures. - implementations: List of labels to Bazel targets representing the actual software - implementation (cc_library, cc_binary, etc.) that realizes the component - requirements. This is the source code that implements the safety functions - as required by ISO 26262-6 clause 8. - tests: List of labels to Bazel test targets (cc_test, py_test, etc.) that verify - the implementation against requirements. Includes unit tests and integration - tests as required by ISO 26262-6 clause 9 for software unit verification. - visibility: Bazel visibility specification for the generated SEooC target. Controls - which other packages can depend on this safety element. - - Generated Targets: - _index: Sphinx environment with generated index.rst and conf.py files - _seooc_index_lib: Sphinx documentation library for the module - : Main SEooC target aggregating all documentation - .html: HTML documentation output - """ - - # Generate index file for the seooc documentation - seooc_sphinx_environment( - name = name + "_index", - module_name = name, - assumptions_of_use = assumptions_of_use, - component_requirements = component_requirements, - architectural_design = architectural_design, - safety_analysis = safety_analysis, - ) - - sphinx_docs_library( - name = name + "_seooc_index_lib", - srcs = [name + "_index"], - prefix = "", - visibility = ["//visibility:public"], - deps = [], - ) - - # Create the main SEooC target - seooc( - name = name, - index = name + "_seooc_index_lib", - assumptions_of_use = assumptions_of_use, - component_requirements = component_requirements, - architectural_design = architectural_design, - safety_analysis = safety_analysis, - visibility = visibility, - ) +load( + "//bazel/rules/score_module/private:score_module.bzl", + _score_module = "score_module", +) +load( + "//bazel/rules/score_module/private:seooc.bzl", + _safety_element_out_of_context = "safety_element_out_of_context", +) + +score_module = _score_module +safety_element_out_of_context = _safety_element_out_of_context diff --git a/bazel/rules/score_module/src/sphinx_html_merge.py b/bazel/rules/score_module/src/sphinx_html_merge.py new file mode 100644 index 0000000..60dfaa4 --- /dev/null +++ b/bazel/rules/score_module/src/sphinx_html_merge.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""Merge multiple Sphinx HTML output directories. + +This script merges Sphinx HTML documentation from multiple modules into a single +output directory. It copies the main module's HTML as-is, and then copies each +dependency module's HTML into a subdirectory, excluding nested module directories +to avoid duplication. + +Usage: + sphinx_html_merge.py --output OUTPUT_DIR --main MAIN_HTML_DIR [--dep NAME:PATH ...] +""" + +import argparse +import os +import re +import shutil +import sys +from pathlib import Path + + +# Standard Sphinx directories that should be copied +# Note: _static and _sphinx_design_static are excluded for dependencies to avoid duplication +SPHINX_DIRS = {"_sources", ".doctrees"} + + +def copy_html_files(src_dir, dst_dir, exclude_module_dirs=None, sibling_modules=None): + """Copy HTML and related files from src to dst, with optional link fixing. + + Args: + src_dir: Source HTML directory + dst_dir: Destination directory + exclude_module_dirs: Set of module directory names to skip (to avoid copying nested modules). + If None, copy everything. + sibling_modules: Set of sibling module names for fixing links in HTML files. + If None, no link fixing is performed. + """ + src_path = Path(src_dir) + dst_path = Path(dst_dir) + + if not src_path.exists(): + print(f"Warning: Source directory does not exist: {src_dir}", file=sys.stderr) + return + + dst_path.mkdir(parents=True, exist_ok=True) + + if exclude_module_dirs is None: + exclude_module_dirs = set() + + # Prepare regex patterns for link fixing if needed + module_pattern = None + static_pattern = None + if sibling_modules: + module_pattern = re.compile( + r'((?:href|src)=")(' + + "|".join(re.escape(mod) for mod in sibling_modules) + + r")/", + re.IGNORECASE, + ) + static_pattern = re.compile( + r'((?:href|src)=")(\.\./)*(_static|_sphinx_design_static)/', re.IGNORECASE + ) + + def process_file(src_file, dst_file, relative_path): + """Read, optionally modify, and write a file.""" + if src_file.suffix == ".html" and sibling_modules: + # Read, modify, and write HTML files + try: + content = src_file.read_text(encoding="utf-8") + + # Replace module_name/ with ../module_name/ + modified_content = module_pattern.sub(r"\1../\2/", content) + + # Calculate depth for static file references + depth = len(relative_path.parents) - 1 + parent_prefix = "../" * (depth + 1) + + def replace_static(match): + return f"{match.group(1)}{parent_prefix}{match.group(3)}/" + + modified_content = static_pattern.sub(replace_static, modified_content) + + # Write modified content + dst_file.parent.mkdir(parents=True, exist_ok=True) + dst_file.write_text(modified_content, encoding="utf-8") + except Exception as e: + print(f"Warning: Failed to process {src_file}: {e}", file=sys.stderr) + # Fallback to regular copy on error + shutil.copy2(src_file, dst_file) + else: + # Regular copy for non-HTML files + dst_file.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src_file, dst_file) + + def copy_tree(src, dst, rel_path): + """Recursively copy directory tree with processing.""" + for item in src.iterdir(): + rel_item = rel_path / item.name + dst_item = dst / item.name + + if item.is_file(): + process_file(item, dst_item, rel_item) + elif item.is_dir(): + # Skip excluded directories + if item.name in exclude_module_dirs: + continue + # Skip static dirs from dependencies + if ( + item.name in ("_static", "_sphinx_design_static") + and exclude_module_dirs + ): + continue + + dst_item.mkdir(parents=True, exist_ok=True) + copy_tree(item, dst_item, rel_item) + + # Start copying from root + copy_tree(src_path, dst_path, Path(".")) + + +def merge_html_dirs(output_dir, main_html_dir, dependencies): + """Merge HTML directories. + + Args: + output_dir: Target output directory + main_html_dir: Main module's HTML directory to copy as-is + dependencies: List of (name, path) tuples for dependency modules + """ + output_path = Path(output_dir) + + # First, copy the main HTML directory + print(f"Copying main HTML from {main_html_dir} to {output_dir}") + copy_html_files(main_html_dir, output_dir) + + # Collect all dependency names for link fixing and exclusion + dep_names = [name for name, _ in dependencies] + + # Then copy each dependency into a subdirectory with link fixing + for dep_name, dep_html_dir in dependencies: + dep_output = output_path / dep_name + print(f"Copying dependency {dep_name} from {dep_html_dir} to {dep_output}") + # Exclude other module directories to avoid nested modules + # Remove current module from the list to get actual siblings to exclude + sibling_modules = set(n for n in dep_names if n != dep_name) + copy_html_files( + dep_html_dir, + dep_output, + exclude_module_dirs=sibling_modules, + sibling_modules=sibling_modules, + ) + + +def main(): + parser = argparse.ArgumentParser( + description="Merge Sphinx HTML documentation directories" + ) + parser.add_argument( + "--output", required=True, help="Output directory for merged HTML" + ) + parser.add_argument("--main", required=True, help="Main HTML directory to copy") + parser.add_argument( + "--dep", + action="append", + default=[], + metavar="NAME:PATH", + help="Dependency HTML directory in format NAME:PATH", + ) + + args = parser.parse_args() + + # Parse dependencies + dependencies = [] + for dep_spec in args.dep: + if ":" not in dep_spec: + print( + f"Error: Invalid dependency format '{dep_spec}', expected NAME:PATH", + file=sys.stderr, + ) + return 1 + + name, path = dep_spec.split(":", 1) + dependencies.append((name, path)) + + # Merge the HTML directories + merge_html_dirs(args.output, args.main, dependencies) + + print(f"Successfully merged HTML into {args.output}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bazel/rules/score_module/src/sphinx_wrapper.py b/bazel/rules/score_module/src/sphinx_wrapper.py new file mode 100644 index 0000000..d00e0b2 --- /dev/null +++ b/bazel/rules/score_module/src/sphinx_wrapper.py @@ -0,0 +1,231 @@ +""" +Wrapper script for running Sphinx builds in Bazel environments. + +This script provides a command-line interface to Sphinx documentation builds, +handling argument parsing, environment configuration, and build execution. +It's designed to be used as part of Bazel build rules for Score modules. +""" + +import argparse +import logging +import os +import sys +import time +from pathlib import Path +from typing import List, Optional + +from sphinx.cmd.build import main as sphinx_main + +# Constants +DEFAULT_PORT = 8000 +DEFAULT_GITHUB_VERSION = "main" +DEFAULT_SOURCE_DIR = "." + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(levelname)s: %(message)s", +) +logger = logging.getLogger(__name__) + + +def get_env(name: str, required: bool = True) -> Optional[str]: + """ + Get an environment variable value. + + Args: + name: The name of the environment variable + required: Whether the variable is required (raises error if not set) + + Returns: + The value of the environment variable, or None if not required and not set + + Raises: + ValueError: If the variable is required but not set + """ + val = os.environ.get(name) + logger.debug(f"Environment variable {name} = {val}") + if val is None and required: + raise ValueError(f"Required environment variable {name} is not set") + return val + + +def validate_arguments(args: argparse.Namespace) -> None: + """ + Validate required command-line arguments. + + Args: + args: Parsed command-line arguments + + Raises: + ValueError: If required arguments are missing or invalid + """ + if not args.index_file: + raise ValueError("--index_file is required") + if not args.output_dir: + raise ValueError("--output_dir is required") + if not args.builder: + raise ValueError("--builder is required") + + # Validate that index file exists if it's a real path + index_path = Path(args.index_file) + if not index_path.exists(): + raise ValueError(f"Index file does not exist: {args.index_file}") + + +def build_sphinx_arguments(args: argparse.Namespace) -> List[str]: + """ + Build the argument list for Sphinx. + + Args: + args: Parsed command-line arguments + + Returns: + List of arguments to pass to Sphinx + """ + source_dir = ( + str(Path(args.index_file).parent) if args.index_file else DEFAULT_SOURCE_DIR + ) + config_dir = str(Path(args.config).parent) if args.config else source_dir + + base_arguments = [ + source_dir, # source dir + args.output_dir, # output dir + "-c", + config_dir, # config directory + # "-W", # treat warning as errors - disabled for modular builds + "--keep-going", # do not abort after one error + "-T", # show details in case of errors in extensions + "--jobs", + "auto", + ] + + # Configure sphinx build with GitHub user and repo from CLI + if args.github_user and args.github_repo: + base_arguments.extend( + [ + f"-A=github_user={args.github_user}", + f"-A=github_repo={args.github_repo}", + f"-A=github_version={DEFAULT_GITHUB_VERSION}", + ] + ) + + # Add doc_path if SOURCE_DIRECTORY environment variable is set + source_directory = get_env("SOURCE_DIRECTORY", required=False) + if source_directory: + base_arguments.append(f"-A=doc_path='{source_directory}'") + + base_arguments.extend(["-b", args.builder]) + + return base_arguments + + +def run_sphinx_build(sphinx_args: List[str], builder: str) -> int: + """ + Execute the Sphinx build and measure duration. + + Args: + sphinx_args: Arguments to pass to Sphinx + builder: The builder type (for logging purposes) + + Returns: + The exit code from Sphinx build + """ + logger.info(f"Starting Sphinx build with builder: {builder}") + logger.debug(f"Sphinx arguments: {sphinx_args}") + + start_time = time.perf_counter() + + try: + exit_code = sphinx_main(sphinx_args) + except Exception as e: + logger.error(f"Sphinx build failed with exception: {e}") + return 1 + + end_time = time.perf_counter() + duration = end_time - start_time + + if exit_code == 0: + logger.info(f"docs ({builder}) finished successfully in {duration:.1f} seconds") + else: + logger.error( + f"docs ({builder}) failed with exit code {exit_code} after {duration:.1f} seconds" + ) + + return exit_code + + +def parse_arguments() -> argparse.Namespace: + """ + Parse command-line arguments. + + Returns: + Parsed command-line arguments + """ + parser = argparse.ArgumentParser( + description="Wrapper for Sphinx documentation builds in Bazel environments" + ) + + # Required arguments + parser.add_argument( + "--index_file", + required=True, + help="Path to the index file (e.g., index.rst)", + ) + parser.add_argument( + "--output_dir", + required=True, + help="Build output directory", + ) + parser.add_argument( + "--builder", + required=True, + help="Sphinx builder to use (e.g., html, needs, json)", + ) + + # Optional arguments + parser.add_argument( + "--config", + help="Path to config file (conf.py)", + ) + parser.add_argument( + "--github_user", + help="GitHub username to embed in the Sphinx build", + ) + parser.add_argument( + "--github_repo", + help="GitHub repository to embed in the Sphinx build", + ) + parser.add_argument( + "--port", + type=int, + default=DEFAULT_PORT, + help=f"Port to use for live preview (default: {DEFAULT_PORT}). Use 0 for auto-detection.", + ) + + return parser.parse_args() + + +def main() -> int: + """ + Main entry point for the Sphinx wrapper script. + + Returns: + Exit code (0 for success, non-zero for failure) + """ + try: + args = parse_arguments() + validate_arguments(args) + sphinx_args = build_sphinx_arguments(args) + exit_code = run_sphinx_build(sphinx_args, args.builder) + return exit_code + except ValueError as e: + logger.error(f"Validation error: {e}") + return 1 + except Exception as e: + logger.error(f"Unexpected error: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bazel/rules/score_module/templates/conf.template.py b/bazel/rules/score_module/templates/conf.template.py new file mode 100644 index 0000000..6f0c188 --- /dev/null +++ b/bazel/rules/score_module/templates/conf.template.py @@ -0,0 +1,192 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Generic Sphinx configuration template for SCORE modules. + +This file is auto-generated from a template and should not be edited directly. +Template variables like {PROJECT_NAME} are replaced during Bazel build. +""" + +import json +import os +from pathlib import Path +from typing import Any, Dict, List + +# Project configuration - {PROJECT_NAME} will be replaced by the module name during build +project = "{PROJECT_NAME}" +author = "S-CORE" +version = "1.0" +release = "1.0.0" +project_url = ( + "https://github.com/eclipse-score" # Required by score_metamodel extension +) + +# Sphinx extensions - comprehensive list for SCORE modules +extensions = [ + "sphinx_needs", + "sphinx_design", + "myst_parser", + "sphinxcontrib.plantuml", + "score_plantuml", + "score_metamodel", + "score_draw_uml_funcs", + "score_source_code_linker", + "score_layout", +] + +# MyST parser extensions +myst_enable_extensions = ["colon_fence"] + +# Exclude patterns for Bazel builds +exclude_patterns = [ + "bazel-*", + ".venv*", +] + +# Enable markdown rendering +source_suffix = { + ".rst": "restructuredtext", + ".md": "markdown", +} + +# Enable numref for cross-references +numfig = True + +# HTML theme +# html_theme = "pydata_sphinx_theme" + + +# Configuration constants +NEEDS_EXTERNAL_FILE = "needs_external_needs.json" +BAZEL_OUT_DIR = "bazel-out" + + +def find_workspace_root() -> Path: + """ + Find the Bazel workspace root by looking for the bazel-out directory. + + Returns: + Path to the workspace root directory + """ + current = Path.cwd() + + # Traverse up the directory tree looking for bazel-out + while current != current.parent: + if (current / BAZEL_OUT_DIR).exists(): + return current + current = current.parent + + # If we reach the root without finding it, return current directory + return Path.cwd() + + +def load_external_needs() -> List[Dict[str, Any]]: + """ + Load external needs configuration from JSON file. + + This function reads the needs_external_needs.json file if it exists and + resolves relative paths to absolute paths based on the workspace root. + + Returns: + List of external needs configurations with resolved paths + """ + needs_file = Path(NEEDS_EXTERNAL_FILE) + + if not needs_file.exists(): + print(f"INFO: {NEEDS_EXTERNAL_FILE} not found - no external dependencies") + return [] + + print(f"INFO: Loading external needs from {NEEDS_EXTERNAL_FILE}") + + try: + with needs_file.open("r", encoding="utf-8") as file: + needs_dict = json.load(file) + except json.JSONDecodeError as e: + print(f"ERROR: Failed to parse {NEEDS_EXTERNAL_FILE}: {e}") + return [] + except Exception as e: + print(f"ERROR: Failed to read {NEEDS_EXTERNAL_FILE}: {e}") + return [] + + workspace_root = find_workspace_root() + print(f"INFO: Workspace root: {workspace_root}") + + external_needs = [] + for key, config in needs_dict.items(): + if "json_path" not in config: + print( + f"WARNING: External needs config for '{key}' missing 'json_path', skipping" + ) + continue + + # Resolve relative path to absolute path + # Bazel provides relative paths like: bazel-out/k8-fastbuild/bin/.../needs.json + # We need absolute paths: .../execroot/_main/bazel-out/... + json_path = workspace_root / config["json_path"] + config["json_path"] = str(json_path) + + print(f"INFO: Added external needs config for '{key}':") + print(f" json_path: {config['json_path']}") + print(f" id_prefix: {config.get('id_prefix', 'none')}") + print(f" version: {config.get('version', 'none')}") + + external_needs.append(config) + + return external_needs + + +def verify_config(app: Any, config: Any) -> None: + """ + Verify that configuration was properly loaded. + + This is called during Sphinx's config-inited event to ensure + external needs configuration is correctly set up. + + Args: + app: Sphinx application object + config: Sphinx configuration object + """ + print("=" * 80) + print("INFO: Verifying Sphinx configuration") + print(f" Project: {config.project}") + print(f" External needs count: {len(config.needs_external_needs)}") + print("=" * 80) + + +def setup(app: Any) -> Dict[str, Any]: + """ + Sphinx setup hook to register event listeners. + + Args: + app: Sphinx application object + + Returns: + Extension metadata dictionary + """ + app.connect("config-inited", verify_config) + + return { + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } + + +# Initialize external needs configuration +print("=" * 80) +print(f"INFO: Sphinx configuration loaded for project: {project}") +print(f"INFO: Current working directory: {Path.cwd()}") + +# Load external needs configuration +needs_external_needs = load_external_needs() diff --git a/bazel/rules/score_module/templates/seooc_index.template.rst b/bazel/rules/score_module/templates/seooc_index.template.rst new file mode 100644 index 0000000..818fdc0 --- /dev/null +++ b/bazel/rules/score_module/templates/seooc_index.template.rst @@ -0,0 +1,26 @@ +.. ******************************************************************************* +.. Copyright (c) 2025 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} + +{description} + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + {architectural_design} + {component_requirements} + {assumptions_of_use} + {safety_analysis} diff --git a/bazel/rules/score_module/test/BUILD b/bazel/rules/score_module/test/BUILD index 95d7b65..d6c94f0 100644 --- a/bazel/rules/score_module/test/BUILD +++ b/bazel/rules/score_module/test/BUILD @@ -10,82 +10,175 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library") -load("//bazel/rules/score_module:score_module.bzl", "safety_element_out_of_context") -load("//bazel/rules/score_module/private:seooc.bzl", "seooc") -load(":score_module_test.bzl", "safety_element_macro_test_suite") -load(":seooc_test.bzl", "seooc_test_suite") +load("//bazel/rules/score_module:score_module.bzl", "safety_element_out_of_context", "score_module") +load( + ":html_generation_test.bzl", + "html_merging_test", + "module_dependencies_test", + "needs_transitive_test", + "score_module_test_suite", +) +load( + ":score_module_providers_test.bzl", + "deps_html_merging_test", + "deps_needs_collection_test", + "score_module_providers_test_suite", + "score_needs_with_deps_test", +) +load( + ":seooc_test.bzl", + "seooc_artifacts_copied_test", + "seooc_index_generation_test", + "seooc_needs_provider_test", + "seooc_score_module_generated_test", +) package(default_visibility = ["//visibility:public"]) -# Test fixtures for seooc tests -sphinx_docs_library( - name = "test_assumptions", - srcs = ["fixtures/assumptions_of_use.rst"], - tags = ["manual"], +# ============================================================================ +# Test Fixtures - Module Definitions +# ============================================================================ + +# Test 1: Multi-Module Aggregation +# Dependency graph: module_a_lib -> module_b_lib -> module_c_lib +# module_a_lib -> module_c_lib (also direct) +score_module( + name = "module_c_lib", + srcs = glob(["fixtures/module_c/*.rst"]), + index = "fixtures/module_c/index.rst", + sphinx = "//bazel/rules/score_module:score_build", ) -sphinx_docs_library( - name = "test_requirements", - srcs = ["fixtures/component_requirements.rst"], - tags = ["manual"], +score_module( + name = "module_b_lib", + srcs = glob(["fixtures/module_b/*.rst"]), + index = "fixtures/module_b/index.rst", + sphinx = "//bazel/rules/score_module:score_build", + deps = [":module_c_lib"], ) -sphinx_docs_library( - name = "test_architecture", - srcs = ["fixtures/architecture_design.rst"], - tags = ["manual"], +score_module( + name = "module_a_lib", + srcs = glob(["fixtures/module_a/*.rst"]), + index = "fixtures/module_a/index.rst", + sphinx = "//bazel/rules/score_module:score_build", + deps = [ + ":module_b_lib", + ":module_c_lib", + ], ) -sphinx_docs_library( - name = "test_safety", - srcs = ["fixtures/safety_analysis.rst"], - tags = ["manual"], +# Test 2: SEooC (Safety Element out of Context) Module +# Tests the safety_element_out_of_context macro with S-CORE process artifacts +safety_element_out_of_context( + name = "seooc_test_lib", + architectural_design = ["fixtures/seooc_test/architectural_design.rst"], + assumptions_of_use = ["fixtures/seooc_test/assumptions_of_use.rst"], + component_requirements = ["fixtures/seooc_test/component_requirements.rst"], + description = "Test SEooC module demonstrating S-CORE process compliance structure.", + safety_analysis = ["fixtures/seooc_test/safety_analysis.rst"], + deps = [ + ":module_c_lib", + ], ) -sphinx_docs_library( - name = "test_index_lib", - srcs = ["fixtures/index.rst"], - tags = ["manual"], +# ============================================================================ +# Test Instantiations - HTML Generation Tests +# ============================================================================ + +# Needs Generation Tests +needs_transitive_test( + name = "needs_transitive_test", + target_under_test = ":module_b_lib_needs", ) -# Minimal seooc test target with all mandatory attributes -seooc( - name = "test_seooc_minimal", - architectural_design = ":test_architecture", - assumptions_of_use = ":test_assumptions", - component_requirements = ":test_requirements", - index = ":test_index_lib", - safety_analysis = ":test_safety", - tags = ["manual"], +# Dependency Tests +module_dependencies_test( + name = "module_dependencies_test", + target_under_test = ":module_a_lib", ) -# Complete seooc test target with all mandatory attributes -seooc( - name = "test_seooc_complete", - architectural_design = ":test_architecture", - assumptions_of_use = ":test_assumptions", - component_requirements = ":test_requirements", - index = ":test_index_lib", - safety_analysis = ":test_safety", - tags = ["manual"], +html_merging_test( + name = "html_merging_test", + target_under_test = ":module_a_lib", ) -# Run the test suite -seooc_test_suite(name = "seooc_tests") +# ============================================================================ +# Test Instantiations - Provider Tests +# ============================================================================ -# Test target using the safety_element_out_of_context macro -# Uses sphinx_docs_library targets that provide SphinxDocsLibraryInfo -safety_element_out_of_context( - name = "test_macro_seooc", - architectural_design = ":test_architecture", - assumptions_of_use = ":test_assumptions", - component_requirements = ":test_requirements", - implementations = None, - safety_analysis = ":test_safety", - tests = None, - visibility = ["//visibility:public"], -) - -# Run the macro test suite -safety_element_macro_test_suite(name = "macro_tests") +# ScoreNeedsInfo Tests +score_needs_with_deps_test( + name = "score_needs_with_deps_test", + target_under_test = ":module_a_lib_needs", +) + +# Dependency Tests +deps_html_merging_test( + name = "deps_html_merging_test", + target_under_test = ":module_a_lib", +) + +deps_needs_collection_test( + name = "deps_needs_collection_test", + target_under_test = ":module_a_lib_needs", +) + +# ============================================================================ +# SEooC-Specific Tests +# ============================================================================ + +# Test that index generation works correctly +seooc_index_generation_test( + name = "seooc_tests_index_generation", + target_under_test = ":seooc_test_lib_seooc_index", +) + +# Test that all artifacts are copied +seooc_artifacts_copied_test( + name = "seooc_tests_artifacts_copied", + target_under_test = ":seooc_test_lib_seooc_index", +) + +# Test that score_module is generated with correct providers +seooc_score_module_generated_test( + name = "seooc_tests_score_module_generated", + target_under_test = ":seooc_test_lib", +) + +# Test that needs provider exists for cross-referencing +seooc_needs_provider_test( + name = "seooc_tests_needs_provider", + target_under_test = ":seooc_test_lib_needs", +) + +# ============================================================================ +# Test Suites +# ============================================================================ + +# Main test suite combining all score_module tests +score_module_test_suite(name = "score_module_tests") + +# Provider-focused test suite +score_module_providers_test_suite(name = "provider_tests") + +# SEooC-focused test suite +test_suite( + name = "seooc_tests", + tests = [ + ":seooc_tests_artifacts_copied", + ":seooc_tests_index_generation", + ":seooc_tests_needs_provider", + ":seooc_tests_score_module_generated", + ], +) + +# Combined test suite for all tests +test_suite( + name = "all_tests", + tests = [ + ":provider_tests", + ":score_module_tests", + ":seooc_tests", + ], +) diff --git a/bazel/rules/score_module/test/README.md b/bazel/rules/score_module/test/README.md deleted file mode 100644 index f85cdc9..0000000 --- a/bazel/rules/score_module/test/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# SEooC Rule Tests - -This directory contains unit tests for the `seooc` rule, which is used to define Safety Elements out of Context (SEooC) following ISO 26262 standards. - -## Test Suite Overview - -The test suite (`seooc_test.bzl`) uses Bazel Skylib's `unittest` framework to verify the correctness of the `seooc` rule implementation. The tests are organized into several categories: - -### Test Categories - -1. **Provider Tests** (`seooc_providers_test`) - - Verifies that the `seooc` rule provides the required providers - - Checks for `DefaultInfo` provider - - Checks for `SphinxDocsLibraryInfo` provider - -2. **Transitive Documentation Tests** (`seooc_transitive_docs_test`) - - Verifies that the rule correctly aggregates transitive documentation - - Ensures that the transitive field is a depset - - Validates the structure of transitive documentation entries - - Confirms that documentation paths start with `docs/safety_elements/` - -3. **Attribute Handling Tests** (`seooc_attributes_test`) - - Verifies that mandatory attributes are correctly handled - - Ensures that at least index documentation is present - -4. **Path Prefixing Tests** (`seooc_path_prefixing_test`) - - Verifies that documentation paths are correctly prefixed with the module name - - Ensures proper path organization for Sphinx documentation - -## Test Fixtures - -The test suite uses the following fixture files located in `fixtures/`: - -- **`assumptions_of_use.rst`**: Sample assumptions of use document -- **`component_requirements.rst`**: Sample component requirements document -- **`index.rst`**: Sample index file for documentation structure - -These fixtures are wrapped as `sphinx_docs_library` targets in the `BUILD` file to create realistic test scenarios. - -## Running the Tests - -To run all seooc tests: - -```bash -bazel test //bazel/rules/score_module/test:seooc_tests -``` - -To run with verbose output: - -```bash -bazel test //bazel/rules/score_module/test:seooc_tests --test_output=all -``` - -To run individual tests: - -```bash -bazel test //bazel/rules/score_module/test:seooc_providers_test -bazel test //bazel/rules/score_module/test:seooc_transitive_docs_test -bazel test //bazel/rules/score_module/test:seooc_attributes_test -bazel test //bazel/rules/score_module/test:seooc_path_prefixing_test -``` - -## Test Targets - -The `BUILD` file defines two test targets: - -1. **`test_seooc_minimal`**: Tests the seooc rule with minimal required attributes - - Only includes `assumptions_of_use` and `index` - - Verifies basic functionality - -2. **`test_seooc_complete`**: Tests the seooc rule with all optional attributes - - Includes all optional documentation attributes - - Verifies handling of complete documentation sets - -## Adding New Tests - -To add a new test: - -1. Define a test implementation function in `seooc_test.bzl`: - - ```python - def _my_new_test_impl(ctx): - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # Your test assertions here - asserts.true(env, condition, "error message") - - return analysistest.end(env) - - my_new_test = analysistest.make(_my_new_test_impl) - ``` - -2. Add the test to `_test_seooc()` function: - - ```python - my_new_test( - name = "my_new_test", - target_under_test = ":test_seooc_minimal", - ) - ``` - -3. Include it in the test suite: - - ```python - native.test_suite( - name = name, - tests = [ - # ... existing tests ... - ":my_new_test", - ], - ) - ``` - -## Test Coverage - -The current test suite covers: - -- ✅ Provider generation -- ✅ Transitive documentation aggregation -- ✅ Mandatory attribute handling -- ✅ Path prefixing with module names -- ✅ Optional attribute handling - -## Dependencies - -The tests depend on: - -- `@bazel_skylib//lib:unittest` - Bazel Skylib testing framework -- `@rules_python//sphinxdocs` - Sphinx documentation rules -- `//bazel/rules/score_module/private:seooc.bzl` - The rule being tested - -## Notes - -- All test targets are tagged with `"manual"` to prevent them from being built during normal builds -- Tests use the `analysistest` framework, which performs analysis-phase validation -- The test suite is part of the continuous integration pipeline diff --git a/bazel/rules/score_module/test/fixtures/architecture_design.rst b/bazel/rules/score_module/test/fixtures/architecture_design.rst deleted file mode 100644 index c73ce0e..0000000 --- a/bazel/rules/score_module/test/fixtures/architecture_design.rst +++ /dev/null @@ -1,46 +0,0 @@ -Architecture Design -=================== - -This document describes the architectural design of the test SEooC module. - -System Architecture -------------------- - -The system consists of the following components: - -* Input Processing Module -* Data Processing Engine -* Output Handler -* Fault Detection and Handling - -Component Interfaces ---------------------- - -Input Processing Module -~~~~~~~~~~~~~~~~~~~~~~~ - -* **Input**: Raw sensor data -* **Output**: Validated and formatted data -* **Interface**: I2C/SPI bus - -Data Processing Engine -~~~~~~~~~~~~~~~~~~~~~~ - -* **Input**: Validated data from Input Processing Module -* **Output**: Processed results -* **Interface**: Internal memory-mapped registers - -Design Decisions ----------------- - -Decision 1: Use of Hardware Watchdog -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The architecture includes a hardware watchdog timer to ensure system reliability -and meet safety requirements REQ-SAFE-001. - -Decision 2: Redundant Processing Paths -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Critical calculations are performed using redundant processing paths to detect -and prevent silent data corruption. diff --git a/bazel/rules/score_module/test/fixtures/assumptions_of_use.rst b/bazel/rules/score_module/test/fixtures/assumptions_of_use.rst deleted file mode 100644 index 11f3785..0000000 --- a/bazel/rules/score_module/test/fixtures/assumptions_of_use.rst +++ /dev/null @@ -1,18 +0,0 @@ -Assumptions of Use -================== - -This document describes the assumptions of use for the test SEooC module. - -Operating Conditions --------------------- - -* Operating temperature: -40°C to +85°C -* Supply voltage: 12V ±10% -* Maximum processing load: 80% - -Environmental Assumptions -------------------------- - -* The system operates in a controlled environment -* No exposure to extreme weather conditions -* Regular maintenance is performed diff --git a/bazel/rules/score_module/test/fixtures/component_requirements.rst b/bazel/rules/score_module/test/fixtures/component_requirements.rst deleted file mode 100644 index 46c38ca..0000000 --- a/bazel/rules/score_module/test/fixtures/component_requirements.rst +++ /dev/null @@ -1,30 +0,0 @@ -Component Requirements -====================== - -This document defines the functional and safety requirements. - -Functional Requirements ------------------------- - -REQ-FUNC-001 -~~~~~~~~~~~~ - -The system shall process input data within 100ms. - -REQ-FUNC-002 -~~~~~~~~~~~~ - -The system shall provide output with 99.9% accuracy. - -Safety Requirements -------------------- - -REQ-SAFE-001 -~~~~~~~~~~~~ - -The system shall detect and handle fault conditions within 50ms. - -REQ-SAFE-002 -~~~~~~~~~~~~ - -The system shall maintain safe state during power loss. diff --git a/bazel/rules/score_module/test/fixtures/index.rst b/bazel/rules/score_module/test/fixtures/index.rst deleted file mode 100644 index 01c37cf..0000000 --- a/bazel/rules/score_module/test/fixtures/index.rst +++ /dev/null @@ -1,13 +0,0 @@ -Test Safety Element -=================== - -This is a test index file for the SEooC test suite. - -Contents --------- - -.. toctree:: - :maxdepth: 2 - - assumptions_of_use - component_requirements diff --git a/bazel/rules/score_module/test/fixtures/module_a/index.rst b/bazel/rules/score_module/test/fixtures/module_a/index.rst new file mode 100644 index 0000000..573ad4b --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/module_a/index.rst @@ -0,0 +1,31 @@ +Module A Documentation +====================== + +This is the documentation for Module A. + +.. document:: Documentation for Module A + :id: doc__module_fixtures_module_a + :status: valid + :safety: ASIL_B + :security: NO + :realizes: wp__component_arch + +Overview +-------- + +Module A is a simple module that depends on Module C. + +Features +-------- + +.. needlist:: + :tags: module_a + +Cross-Module References +----------------------- + +General reference to Module C :external+module_c_lib:doc:`index`. + +Need reference to Module C :need:`doc__module_fixtures_module_c`. + +Need reference to Module B :need:`doc__module_fixtures_module_b`. diff --git a/bazel/rules/score_module/test/fixtures/module_b/index.rst b/bazel/rules/score_module/test/fixtures/module_b/index.rst new file mode 100644 index 0000000..3155c10 --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/module_b/index.rst @@ -0,0 +1,37 @@ +Module B Documentation +====================== + +This is the documentation for Module B. + +.. document:: Documentation for Module B + :id: doc__module_fixtures_module_b + :status: valid + :safety: ASIL_B + :security: NO + :realizes: + +Overview +-------- + +Module B depends on both Module A and Module C. + +Features +-------- + +.. needlist:: + :tags: module_b + +Cross-Module References +----------------------- + +This module references: + +* :external+module_a_lib:doc:`index` from Module A +* :external+module_c_lib:doc:`index` from Module C +* Need reference to Module C :need:`doc__module_fixtures_module_c` +* Need reference to Module C :need:`doc__module_fixtures_module_d` + +Dependencies +------------ + +Module B integrates functionality from both dependent modules. diff --git a/bazel/rules/score_module/test/fixtures/module_c/index.rst b/bazel/rules/score_module/test/fixtures/module_c/index.rst new file mode 100644 index 0000000..b73ae61 --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/module_c/index.rst @@ -0,0 +1,29 @@ +Module C Documentation +====================== + +This is the documentation for Module C. + +.. document:: Documentation for Module C + :id: doc__module_fixtures_module_c + :status: valid + :safety: ASIL_B + :security: NO + :realizes: + + +Overview +-------- + +Module C is a base module with no dependencies. +Local need link: :need:`doc__module_fixtures_module_c` + +Features +-------- + +.. needlist:: + :tags: module_c + +Content +------- + +Module C provides foundational functionality used by other modules. diff --git a/bazel/rules/score_module/test/fixtures/safety_analysis.rst b/bazel/rules/score_module/test/fixtures/safety_analysis.rst deleted file mode 100644 index 54b8908..0000000 --- a/bazel/rules/score_module/test/fixtures/safety_analysis.rst +++ /dev/null @@ -1,57 +0,0 @@ -Safety Analysis -=============== - -This document contains the safety analysis for the test SEooC module. - -Failure Mode and Effects Analysis (FMEA) ------------------------------------------ - -FMEA-001: Input Data Corruption -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* **Failure Mode**: Corrupted input data from sensors -* **Effect**: Incorrect processing results -* **Severity**: High -* **Detection Method**: CRC checksum validation -* **Mitigation**: Reject invalid data and enter safe state - -FMEA-002: Processing Timeout -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* **Failure Mode**: Processing exceeds time deadline -* **Effect**: System becomes unresponsive -* **Severity**: Medium -* **Detection Method**: Watchdog timer -* **Mitigation**: System reset and recovery - -Fault Tree Analysis (FTA) --------------------------- - -Top Event: System Failure -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following events can lead to system failure: - -* Hardware failure (probability: 1e-6) -* Software defect (probability: 1e-5) -* External interference (probability: 1e-7) - -**Total failure probability**: 1.11e-5 per hour - -Safety Measures ---------------- - -SM-001: Input Validation -~~~~~~~~~~~~~~~~~~~~~~~~~ - -All input data is validated before processing to prevent invalid data propagation. - -SM-002: Periodic Self-Test -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The system performs periodic self-tests to detect latent faults. - -SM-003: Safe State Transition -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Upon detection of critical faults, the system transitions to a predefined safe state. diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/architectural_design.rst b/bazel/rules/score_module/test/fixtures/seooc_test/architectural_design.rst new file mode 100644 index 0000000..02e96f7 --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/seooc_test/architectural_design.rst @@ -0,0 +1,174 @@ +Architectural Design +==================== + +This document describes the architectural design of the test SEooC module. + +Software Architecture Overview +------------------------------- + +The system consists of the following software components: + +.. comp_arc_sta:: Input Processing Module + :id: comp_arc_sta__seooc_test__input_processing_module + :status: valid + :tags: architecture, component, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__input_data_processing, comp_req__seooc_test__can_message_reception + + Responsible for receiving and validating input data from CAN interface. + + **Inputs**: Raw CAN messages + + **Outputs**: Validated data structures + + **Safety Mechanisms**: CRC validation, sequence counter check + +.. comp_arc_sta:: Data Processing Engine + :id: comp_arc_sta__seooc_test__data_processing_engine + :status: valid + :tags: architecture, component, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__output_accuracy, comp_req__seooc_test__redundant_calculation + + Core processing component that performs calculations on validated data. + + **Inputs**: Validated data from Input Processing Module + + **Outputs**: Processed results + + **Safety Mechanisms**: Dual-channel redundant calculation + +.. comp_arc_sta:: Output Handler + :id: comp_arc_sta__seooc_test__output_handler + :status: valid + :tags: architecture, component, seooc_test + :safety: QM + :security: NO + :fulfils: comp_req__seooc_test__can_message_transmission + + Formats and transmits output data via CAN interface. + + **Inputs**: Processed results from Data Processing Engine + + **Outputs**: CAN messages + + **Safety Mechanisms**: Message sequence numbering, alive counter + +.. comp_arc_sta:: Fault Detection and Handling + :id: comp_arc_sta__seooc_test__fault_detection_handling + :status: valid + :tags: architecture, component, safety, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__fault_detection, comp_req__seooc_test__safe_state_transition + + Monitors system health and handles fault conditions. + + **Inputs**: Status from all components + + **Outputs**: System state, error flags + + **Safety Mechanisms**: Watchdog timer, plausibility checks + +Component Interfaces +--------------------- + +Interface: CAN Communication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. real_arc_int:: CAN RX Interface + :id: real_arc_int__seooc_test__can_rx + :status: valid + :tags: interface, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__can_message_reception + :language: cpp + + * **Protocol**: CAN 2.0B + * **Baud Rate**: 500 kbps + * **Message ID Range**: 0x100-0x1FF + * **DLC**: 8 bytes + +.. real_arc_int:: CAN TX Interface + :id: real_arc_int__seooc_test__can_tx + :status: valid + :tags: interface, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__can_message_transmission + :language: cpp + + * **Protocol**: CAN 2.0B + * **Baud Rate**: 500 kbps + * **Message ID Range**: 0x200-0x2FF + * **DLC**: 8 bytes + +Design Decisions +---------------- + +.. comp_arc_dyn:: Use of Hardware Watchdog + :id: comp_arc_dyn__seooc_test__hw_watchdog + :status: valid + :tags: design-decision, safety, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__fault_detection + + The architecture includes a hardware watchdog timer to ensure system + reliability and meet safety requirements. + + **Rationale**: Hardware watchdog provides independent monitoring + of software execution and can detect timing violations. + + **Alternatives Considered**: Software-only monitoring (rejected due + to lower ASIL coverage) + +.. comp_arc_dyn:: Redundant Processing Paths + :id: comp_arc_dyn__seooc_test__redundancy + :status: valid + :tags: design-decision, safety, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__redundant_calculation + + Critical calculations are performed using redundant processing paths + to detect and prevent silent data corruption. + + **Rationale**: Meets ASIL-B requirements for detection of random + hardware faults during calculation. + + **Implementation**: Main path + shadow path with result comparison + +Memory Architecture +------------------- + +.. comp_arc_sta:: RAM Allocation + :id: comp_arc_sta__seooc_test__ram_allocation + :status: valid + :tags: resource, memory, seooc_test + :safety: QM + :security: NO + :fulfils: aou_req__seooc_test__memory_requirements + + * **Total RAM**: 512 KB + * **Stack**: 64 KB + * **Heap**: 128 KB + * **Static Data**: 256 KB + * **Reserved**: 64 KB + +.. comp_arc_sta:: Flash Allocation + :id: comp_arc_sta__seooc_test__flash_allocation + :status: valid + :tags: resource, memory, seooc_test + :safety: QM + :security: NO + :fulfils: aou_req__seooc_test__memory_requirements + + * **Total Flash**: 2 MB + * **Application Code**: 1.5 MB + * **Configuration Data**: 256 KB + * **Boot Loader**: 128 KB + * **Reserved**: 128 KB diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/assumptions_of_use.rst b/bazel/rules/score_module/test/fixtures/seooc_test/assumptions_of_use.rst new file mode 100644 index 0000000..fae172c --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/seooc_test/assumptions_of_use.rst @@ -0,0 +1,80 @@ +Assumptions of Use +================== + +This document describes the assumptions of use for the test SEooC module. + +.. aou_req:: Operating Temperature Range + :id: aou_req__seooc_test__operating_temperature_range + :status: valid + :tags: environment, iso26262, seooc_test + :safety: ASIL_B + :security: NO + + The SEooC shall operate within temperature range -40°C to +85°C. + +.. aou_req:: Supply Voltage + :id: aou_req__seooc_test__supply_voltage + :status: valid + :tags: power, iso26262, seooc_test + :safety: ASIL_B + :security: NO + + The SEooC shall operate with supply voltage 12V ±10%. + + Maximum current consumption: 2.5A + +.. aou_req:: Processing Load + :id: aou_req__seooc_test__processing_load + :status: valid + :tags: performance, iso26262, seooc_test + :safety: ASIL_B + :security: NO + + The maximum processing load shall not exceed 80% to ensure + timing requirements are met. + +Environmental Assumptions +------------------------- + +.. aou_req:: Controlled Environment + :id: aou_req__seooc_test__controlled_environment + :status: valid + :tags: environment, seooc_test + :safety: ASIL_B + :security: NO + + The system operates in a controlled automotive environment + compliant with ISO 16750 standards. + +.. aou_req:: Maintenance + :id: aou_req__seooc_test__maintenance + :status: valid + :tags: maintenance, seooc_test + :safety: ASIL_B + :security: NO + + Regular maintenance is performed according to the maintenance + schedule defined in the integration manual. + +Integration Constraints +----------------------- + +.. aou_req:: CAN Bus Interface + :id: aou_req__seooc_test__can_bus_interface + :status: valid + :tags: interface, communication, seooc_test + :safety: ASIL_B + :security: NO + + The host system shall provide a CAN 2.0B compliant interface + for communication with the SEooC. + +.. aou_req:: Memory Requirements + :id: aou_req__seooc_test__memory_requirements + :status: valid + :tags: resource, seooc_test + :safety: ASIL_B + :security: NO + + The host system shall provide at least 512KB of RAM and + 2MB of flash memory for the SEooC. diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/component_requirements.rst b/bazel/rules/score_module/test/fixtures/seooc_test/component_requirements.rst new file mode 100644 index 0000000..1d7f90c --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/seooc_test/component_requirements.rst @@ -0,0 +1,105 @@ +Component Requirements +====================== + +This document defines the functional and safety requirements. + +Functional Requirements +------------------------ + +.. comp_req:: Input Data Processing + :id: comp_req__seooc_test__input_data_processing + :status: valid + :tags: functional, performance, seooc_test + :safety: QM + :security: NO + :satisfies: aou_req__seooc_test__processing_load + + The system shall process input data within 100ms from reception. + + **Rationale**: Real-time processing required for control loop. + +.. comp_req:: Output Accuracy + :id: comp_req__seooc_test__output_accuracy + :status: valid + :tags: functional, quality, seooc_test + :safety: QM + :security: NO + + The system shall provide output with 99.9% accuracy under + nominal operating conditions. + +.. comp_req:: Data Logging + :id: comp_req__seooc_test__data_logging + :status: valid + :tags: functional, diagnostic, seooc_test + :safety: QM + :security: NO + + The system shall log all error events with timestamp and + error code to non-volatile memory. + +Safety Requirements +------------------- + +.. comp_req:: Fault Detection + :id: comp_req__seooc_test__fault_detection + :status: valid + :tags: safety, seooc_test + :safety: ASIL_B + :security: NO + :satisfies: aou_req__seooc_test__processing_load + + The system shall detect and handle fault conditions within 50ms. + + **ASIL Level**: ASIL-B + **Safety Mechanism**: Watchdog timer + plausibility checks + +.. comp_req:: Safe State Transition + :id: comp_req__seooc_test__safe_state_transition + :status: valid + :tags: safety, seooc_test + :safety: ASIL_B + :security: NO + + The system shall maintain safe state during power loss and + complete shutdown within 20ms. + + **ASIL Level**: ASIL-B + **Safe State**: All outputs disabled, error flag set + +.. comp_req:: Redundant Calculation + :id: comp_req__seooc_test__redundant_calculation + :status: valid + :tags: safety, seooc_test + :safety: ASIL_B + :security: NO + + Critical calculations shall be performed using redundant + processing paths with comparison. + + **ASIL Level**: ASIL-B + **Safety Mechanism**: Dual-channel processing + +Communication Requirements +--------------------------- + +.. comp_req:: CAN Message Transmission + :id: comp_req__seooc_test__can_message_transmission + :status: valid + :tags: functional, communication, seooc_test + :safety: QM + :security: NO + :satisfies: aou_req__seooc_test__can_bus_interface + + The system shall transmit status messages on CAN bus + every 100ms ±10ms. + +.. comp_req:: CAN Message Reception + :id: comp_req__seooc_test__can_message_reception + :status: valid + :tags: functional, communication, seooc_test + :safety: QM + :security: NO + :satisfies: aou_req__seooc_test__can_bus_interface + + The system shall process received CAN messages within 10ms. diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/safety_analysis.rst b/bazel/rules/score_module/test/fixtures/seooc_test/safety_analysis.rst new file mode 100644 index 0000000..ea5b518 --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/seooc_test/safety_analysis.rst @@ -0,0 +1,292 @@ +Safety Analysis +=============== + +This document contains the safety analysis for the test SEooC module. + +Failure Mode and Effects Analysis (FMEA) +----------------------------------------- + +.. comp_saf_fmea:: Input Data Corruption + :id: comp_saf_fmea__seooc_test__input_data_corruption + :status: valid + :tags: fmea, safety, seooc_test + :violates: comp_arc_sta__seooc_test__input_processing_module + :fault_id: bit_flip + :failure_effect: Corrupted input data from CAN bus due to electromagnetic interference, transmission errors, or faulty sensor leading to incorrect processing results + :mitigated_by: comp_req__seooc_test__fault_detection + :sufficient: yes + + **Failure Mode**: Corrupted input data from CAN bus + + **Potential Causes**: + + * Electromagnetic interference + * Transmission errors + * Faulty sensor + + **Effects**: Incorrect processing results, potential unsafe output + + **Severity**: High (S9) + + **Occurrence**: Medium (O4) + + **Detection**: High (D2) + + **RPN**: 72 + + **Detection Method**: CRC checksum validation, sequence counter check + + **Mitigation**: Reject invalid data and enter safe state within 50ms + +.. comp_saf_fmea:: Processing Timeout + :id: comp_saf_fmea__seooc_test__processing_timeout + :status: valid + :tags: fmea, safety, seooc_test + :violates: comp_arc_sta__seooc_test__fault_detection_handling + :fault_id: timing_failure + :failure_effect: Processing exceeds time deadline due to software defect, CPU overload, or hardware fault causing system unresponsiveness + :mitigated_by: comp_req__seooc_test__fault_detection + :sufficient: yes + + **Failure Mode**: Processing exceeds time deadline + + **Potential Causes**: + + * Software defect (infinite loop) + * CPU overload + * Hardware fault + + **Effects**: System becomes unresponsive, watchdog reset + + **Severity**: Medium (S6) + + **Occurrence**: Low (O3) + + **Detection**: Very High (D1) + + **RPN**: 18 + + **Detection Method**: Hardware watchdog timer + + **Mitigation**: System reset and recovery to safe state + +.. comp_saf_fmea:: Calculation Error + :id: comp_saf_fmea__seooc_test__calculation_error + :status: valid + :tags: fmea, safety, seooc_test + :violates: comp_arc_sta__seooc_test__data_processing_engine + :fault_id: seu + :failure_effect: Incorrect calculation result due to single event upset, register corruption, or ALU malfunction + :mitigated_by: comp_req__seooc_test__redundant_calculation + :sufficient: yes + + **Failure Mode**: Incorrect calculation result due to random hardware fault + + **Potential Causes**: + + * Single event upset (SEU) + * Register corruption + * ALU malfunction + + **Effects**: Incorrect output values + + **Severity**: High (S8) + + **Occurrence**: Very Low (O2) + + **Detection**: High (D2) + + **RPN**: 32 + + **Detection Method**: Dual-channel redundant calculation with comparison + + **Mitigation**: Discard result and use previous valid value, set error flag + +Dependent Failure Analysis (DFA) +--------------------------------- + +.. comp_saf_dfa:: System Failure Top Event + :id: comp_saf_dfa__seooc_test__system_failure_top + :status: valid + :tags: dfa, safety, seooc_test + :violates: comp_arc_sta__seooc_test__data_processing_engine + :failure_id: common_cause + :failure_effect: System provides unsafe output due to common cause failures affecting multiple safety mechanisms simultaneously + :mitigated_by: aou_req__seooc_test__controlled_environment + :sufficient: yes + + **Top Event**: System provides unsafe output + + **Goal**: Probability < 1e-6 per hour (ASIL-B target) + +.. comp_saf_dfa:: Hardware Failure Branch + :id: comp_saf_dfa__seooc_test__hw_failure + :status: valid + :tags: dfa, safety, seooc_test + :violates: comp_arc_sta__seooc_test__data_processing_engine + :failure_id: hw_common_mode + :failure_effect: Hardware component failures due to common cause (overvoltage, overtemperature) affecting multiple components + :mitigated_by: aou_req__seooc_test__operating_temperature_range, aou_req__seooc_test__supply_voltage + :sufficient: yes + + **Event**: Hardware component failure + + **Sub-events**: + + * Microcontroller failure (λ = 5e-7) + * Power supply failure (λ = 3e-7) + * CAN transceiver failure (λ = 2e-7) + + **Combined Probability**: 1.0e-6 per hour + +.. comp_saf_dfa:: Software Failure Branch + :id: comp_saf_dfa__seooc_test__sw_failure + :status: valid + :tags: dfa, safety, seooc_test + :violates: comp_arc_sta__seooc_test__data_processing_engine + :failure_id: sw_systematic + :failure_effect: Software defect affecting both processing channels due to systematic fault in common code base + :mitigated_by: comp_req__seooc_test__redundant_calculation + :sufficient: yes + + **Event**: Software defect leads to unsafe output + + **Sub-events**: + + * Undetected software bug (λ = 8e-6, detection coverage 90%) + * Memory corruption (λ = 1e-7) + + **Combined Probability**: 9e-7 per hour (after detection coverage) + +.. comp_saf_dfa:: External Interference Branch + :id: comp_saf_dfa__seooc_test__ext_interference + :status: valid + :tags: dfa, safety, seooc_test + :violates: comp_arc_sta__seooc_test__input_processing_module + :failure_id: emi + :failure_effect: External interference causing simultaneous malfunction of multiple components + :mitigated_by: aou_req__seooc_test__controlled_environment + :sufficient: yes + + **Event**: External interference causes malfunction + + **Sub-events**: + + * EMI beyond specification (λ = 5e-8) + * Voltage transient (λ = 2e-8, mitigation 99%) + + **Combined Probability**: 5.2e-8 per hour (after mitigation) + +**Total System Failure Probability**: 1.95e-6 per hour + +**ASIL-B Target**: < 1e-5 per hour ✓ **PASSED** + +Safety Mechanisms +----------------- + +.. comp_arc_sta:: SM: Input Validation + :id: comp_arc_sta__seooc_test__sm_input_validation + :status: valid + :tags: safety-mechanism, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__fault_detection + + **Description**: All input data is validated before processing + + **Checks Performed**: + + * CRC-16 checksum validation + * Message sequence counter verification + * Data range plausibility checks + + **Diagnostic Coverage**: 95% + + **Reaction**: Reject invalid data, increment error counter, use last valid value + +.. comp_arc_sta:: SM: Watchdog Timer + :id: comp_arc_sta__seooc_test__sm_watchdog + :status: valid + :tags: safety-mechanism, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__fault_detection + + **Description**: Hardware watchdog monitors software execution + + **Configuration**: + + * Timeout: 150ms + * Window watchdog: 100-140ms trigger window + * Reset delay: 10ms + + **Diagnostic Coverage**: 99% + + **Reaction**: System reset, boot to safe state + +.. comp_arc_sta:: SM: Redundant Calculation + :id: comp_arc_sta__seooc_test__sm_redundant_calc + :status: valid + :tags: safety-mechanism, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__redundant_calculation + + **Description**: Critical calculations performed in dual channels + + **Implementation**: + + * Main calculation path + * Independent shadow path + * Result comparison with tolerance check + + **Diagnostic Coverage**: 98% + + **Reaction**: On mismatch, use previous valid value, set error flag + +Safety Validation Results +-------------------------- + +.. comp_arc_dyn:: Validation: FMEA Coverage + :id: comp_arc_dyn__seooc_test__val_fmea_coverage + :status: valid + :tags: validation, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__fault_detection + + **Result**: All identified failure modes have detection mechanisms + + **Coverage**: 100% of critical failure modes + + **Status**: ✓ PASSED + +.. comp_arc_dyn:: Validation: DFA Target Achievement + :id: comp_arc_dyn__seooc_test__val_dfa_target + :status: valid + :tags: validation, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__safe_state_transition + + **Result**: System failure probability 1.95e-6 per hour + + **Target**: < 1e-5 per hour (ASIL-B) + + **Margin**: 5.1x + + **Status**: ✓ PASSED + +.. comp_arc_dyn:: Validation: Safety Mechanism Effectiveness + :id: comp_arc_dyn__seooc_test__val_sm_effectiveness + :status: valid + :tags: validation, seooc_test + :safety: ASIL_B + :security: NO + :fulfils: comp_req__seooc_test__redundant_calculation + + **Result**: Combined diagnostic coverage 97.3% + + **Target**: > 90% (ASIL-B) + + **Status**: ✓ PASSED diff --git a/bazel/rules/score_module/test/html_generation_test.bzl b/bazel/rules/score_module/test/html_generation_test.bzl new file mode 100644 index 0000000..0a0b1ee --- /dev/null +++ b/bazel/rules/score_module/test/html_generation_test.bzl @@ -0,0 +1,223 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* +"""Test rules for score_module HTML generation and dependencies.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("//bazel/rules/score_module/private:score_module.bzl", "ScoreModuleInfo", "ScoreNeedsInfo") + +# ============================================================================ +# Provider Tests +# ============================================================================ + +def _providers_test_impl(ctx): + """Test that score_module provides the correct providers.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Verify required providers + asserts.true( + env, + ScoreModuleInfo in target_under_test, + "Target should provide ScoreModuleInfo", + ) + + asserts.true( + env, + DefaultInfo in target_under_test, + "Target should provide DefaultInfo", + ) + + return analysistest.end(env) + +providers_test = analysistest.make(_providers_test_impl) + +# ============================================================================ +# HTML Generation Tests +# ============================================================================ + +def _basic_html_generation_test_impl(ctx): + """Test that a simple document generates HTML output.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check that HTML directory exists + score_info = target_under_test[ScoreModuleInfo] + asserts.true( + env, + score_info.html_dir != None, + "Module should generate HTML directory", + ) + + return analysistest.end(env) + +basic_html_generation_test = analysistest.make(_basic_html_generation_test_impl) + +# ============================================================================ +# Needs.json Generation Tests +# ============================================================================ + +def _needs_generation_test_impl(ctx): + """Test that score_module generates needs.json files.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check for ScoreNeedsInfo provider on _needs target + # Note: This test requires the _needs suffix target + asserts.true( + env, + DefaultInfo in target_under_test, + "Needs target should provide DefaultInfo", + ) + + return analysistest.end(env) + +needs_generation_test = analysistest.make(_needs_generation_test_impl) + +def _needs_transitive_test_impl(ctx): + """Test that needs.json files are collected transitively.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Verify ScoreNeedsInfo provider + asserts.true( + env, + ScoreNeedsInfo in target_under_test, + "Needs target should provide ScoreNeedsInfo", + ) + + needs_info = target_under_test[ScoreNeedsInfo] + + # Check direct needs.json file + asserts.true( + env, + needs_info.needs_json_file != None, + "Should have direct needs.json file", + ) + + # Check transitive needs collection + asserts.true( + env, + needs_info.needs_json_files != None, + "Should have transitive needs.json files depset", + ) + + return analysistest.end(env) + +needs_transitive_test = analysistest.make(_needs_transitive_test_impl) + +# ============================================================================ +# Dependency and Integration Tests +# ============================================================================ + +def _module_dependencies_test_impl(ctx): + """Test that module dependencies are properly handled.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + score_info = target_under_test[ScoreModuleInfo] + + # Module with dependencies should still generate HTML + asserts.true( + env, + score_info.html_dir != None, + "Module with dependencies should generate HTML", + ) + + return analysistest.end(env) + +module_dependencies_test = analysistest.make(_module_dependencies_test_impl) + +def _html_merging_test_impl(ctx): + """Test that HTML from dependencies is merged correctly.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + score_info = target_under_test[ScoreModuleInfo] + + # Verify merged HTML output exists + asserts.true( + env, + score_info.html_dir != None, + "Merged HTML should be generated", + ) + + return analysistest.end(env) + +html_merging_test = analysistest.make(_html_merging_test_impl) + +# ============================================================================ +# Config Generation Tests +# ============================================================================ + +def _auto_config_generation_test_impl(ctx): + """Test that conf.py is automatically generated when not provided.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + score_info = target_under_test[ScoreModuleInfo] + + # Module without explicit config should still generate HTML + asserts.true( + env, + score_info.html_dir != None, + "Module with auto-generated config should produce HTML", + ) + + return analysistest.end(env) + +auto_config_generation_test = analysistest.make(_auto_config_generation_test_impl) + +def _explicit_config_test_impl(ctx): + """Test that explicit conf.py is used when provided.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + score_info = target_under_test[ScoreModuleInfo] + + # Module with explicit config should generate HTML + asserts.true( + env, + score_info.html_dir != None, + "Module with explicit config should produce HTML", + ) + + return analysistest.end(env) + +explicit_config_test = analysistest.make(_explicit_config_test_impl) + +# ============================================================================ +# Test Suite +# ============================================================================ + +def score_module_test_suite(name): + """Create a comprehensive test suite for score_module. + + Tests cover: + - Needs.json generation and transitive collection + - Module dependencies and HTML merging + + Args: + name: Name of the test suite + """ + + native.test_suite( + name = name, + tests = [ + # Needs generation + ":needs_transitive_test", + + # Dependencies and integration + ":module_dependencies_test", + ":html_merging_test", + ], + ) diff --git a/bazel/rules/score_module/test/score_module_providers_test.bzl b/bazel/rules/score_module/test/score_module_providers_test.bzl new file mode 100644 index 0000000..6344105 --- /dev/null +++ b/bazel/rules/score_module/test/score_module_providers_test.bzl @@ -0,0 +1,323 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* +"""Tests for score_module providers and two-phase build system.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("//bazel/rules/score_module/private:score_module.bzl", "ScoreModuleInfo", "ScoreNeedsInfo") + +# ============================================================================ +# ScoreModuleInfo Provider Tests +# ============================================================================ + +def _score_module_info_fields_test_impl(ctx): + """Test that ScoreModuleInfo provides all required fields.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + asserts.true( + env, + ScoreModuleInfo in target_under_test, + "Target should provide ScoreModuleInfo", + ) + + score_info = target_under_test[ScoreModuleInfo] + + # Verify html_dir field + asserts.true( + env, + hasattr(score_info, "html_dir"), + "ScoreModuleInfo should have html_dir field", + ) + + asserts.true( + env, + score_info.html_dir != None, + "html_dir should not be None", + ) + + return analysistest.end(env) + +score_module_info_fields_test = analysistest.make(_score_module_info_fields_test_impl) + +# ============================================================================ +# ScoreNeedsInfo Provider Tests +# ============================================================================ + +def _score_needs_info_fields_test_impl(ctx): + """Test that ScoreNeedsInfo provides all required fields.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + asserts.true( + env, + ScoreNeedsInfo in target_under_test, + "Needs target should provide ScoreNeedsInfo", + ) + + needs_info = target_under_test[ScoreNeedsInfo] + + # Verify needs_json_file field (direct file) + asserts.true( + env, + hasattr(needs_info, "needs_json_file"), + "ScoreNeedsInfo should have needs_json_file field", + ) + + asserts.true( + env, + needs_info.needs_json_file != None, + "needs_json_file should not be None", + ) + + # Verify needs_json_files field (transitive depset) + asserts.true( + env, + hasattr(needs_info, "needs_json_files"), + "ScoreNeedsInfo should have needs_json_files field", + ) + + asserts.true( + env, + needs_info.needs_json_files != None, + "needs_json_files should not be None", + ) + + # Verify it's a depset + asserts.true( + env, + type(needs_info.needs_json_files) == type(depset([])), + "needs_json_files should be a depset", + ) + + return analysistest.end(env) + +score_needs_info_fields_test = analysistest.make(_score_needs_info_fields_test_impl) + +def _score_needs_transitive_collection_test_impl(ctx): + """Test that needs.json files are collected transitively.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + needs_info = target_under_test[ScoreNeedsInfo] + + # Get the list of transitive needs files + transitive_needs = needs_info.needs_json_files.to_list() + + # Should have at least the direct needs file + asserts.true( + env, + len(transitive_needs) >= 1, + "Should have at least the direct needs.json file", + ) + + # Direct file should be in the transitive set + direct_file = needs_info.needs_json_file + asserts.true( + env, + direct_file in transitive_needs, + "Direct needs.json file should be in transitive collection", + ) + + return analysistest.end(env) + +score_needs_transitive_collection_test = analysistest.make(_score_needs_transitive_collection_test_impl) + +def _score_needs_with_deps_test_impl(ctx): + """Test that needs.json files include dependencies.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + needs_info = target_under_test[ScoreNeedsInfo] + transitive_needs = needs_info.needs_json_files.to_list() + + # Module with dependencies should have multiple needs files + # (its own + dependencies) + asserts.true( + env, + len(transitive_needs) >= 1, + "Module with dependencies should collect transitive needs.json files", + ) + + return analysistest.end(env) + +score_needs_with_deps_test = analysistest.make(_score_needs_with_deps_test_impl) + +# ============================================================================ +# Two-Phase Build Tests +# ============================================================================ + +def _two_phase_needs_first_test_impl(ctx): + """Test that Phase 1 (needs generation) works independently.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Verify ScoreNeedsInfo provider + asserts.true( + env, + ScoreNeedsInfo in target_under_test, + "Phase 1 should provide ScoreNeedsInfo", + ) + + # Verify DefaultInfo with needs.json output + asserts.true( + env, + DefaultInfo in target_under_test, + "Phase 1 should provide DefaultInfo", + ) + + default_info = target_under_test[DefaultInfo] + files = default_info.files.to_list() + + # Should have at least one file (needs.json) + asserts.true( + env, + len(files) >= 1, + "Phase 1 should output needs.json file", + ) + + return analysistest.end(env) + +two_phase_needs_first_test = analysistest.make(_two_phase_needs_first_test_impl) + +def _two_phase_html_second_test_impl(ctx): + """Test that Phase 2 (HTML generation) works with needs from Phase 1.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Verify ScoreModuleInfo provider + asserts.true( + env, + ScoreModuleInfo in target_under_test, + "Phase 2 should provide ScoreModuleInfo", + ) + + score_info = target_under_test[ScoreModuleInfo] + + # Verify HTML output + asserts.true( + env, + score_info.html_dir != None, + "Phase 2 should generate HTML directory", + ) + + return analysistest.end(env) + +two_phase_html_second_test = analysistest.make(_two_phase_html_second_test_impl) + +# ============================================================================ +# Config Generation Tests +# ============================================================================ + +def _config_auto_generation_test_impl(ctx): + """Test that conf.py is auto-generated when not provided.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + score_info = target_under_test[ScoreModuleInfo] + + # Module without explicit config should still build + asserts.true( + env, + score_info.html_dir != None, + "Auto-generated config should allow HTML generation", + ) + + return analysistest.end(env) + +config_auto_generation_test = analysistest.make(_config_auto_generation_test_impl) + +def _config_explicit_usage_test_impl(ctx): + """Test that explicit conf.py is used when provided.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + score_info = target_under_test[ScoreModuleInfo] + + # Module with explicit config should build + asserts.true( + env, + score_info.html_dir != None, + "Explicit config should allow HTML generation", + ) + + return analysistest.end(env) + +config_explicit_usage_test = analysistest.make(_config_explicit_usage_test_impl) + +# ============================================================================ +# Dependency Handling Tests +# ============================================================================ + +def _deps_html_merging_test_impl(ctx): + """Test that HTML from dependencies is merged into output.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + score_info = target_under_test[ScoreModuleInfo] + + # Module with dependencies should generate merged HTML + asserts.true( + env, + score_info.html_dir != None, + "Module with dependencies should generate merged HTML", + ) + + return analysistest.end(env) + +deps_html_merging_test = analysistest.make(_deps_html_merging_test_impl) + +def _deps_needs_collection_test_impl(ctx): + """Test that needs from dependencies are collected.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + needs_info = target_under_test[ScoreNeedsInfo] + transitive_needs = needs_info.needs_json_files.to_list() + + # Should collect needs from dependencies + asserts.true( + env, + len(transitive_needs) >= 1, + "Should collect needs.json from dependencies", + ) + + return analysistest.end(env) + +deps_needs_collection_test = analysistest.make(_deps_needs_collection_test_impl) + +# ============================================================================ +# Test Suite +# ============================================================================ + +def score_module_providers_test_suite(name): + """Create a test suite for score_module providers and build phases. + + Tests cover: + - Transitive needs.json collection + - Dependency handling (HTML merging, needs collection) + + Args: + name: Name of the test suite + """ + + native.test_suite( + name = name, + tests = [ + # Provider tests + ":score_needs_with_deps_test", + + # Dependency tests + ":deps_html_merging_test", + ":deps_needs_collection_test", + ], + ) diff --git a/bazel/rules/score_module/test/score_module_test.bzl b/bazel/rules/score_module/test/score_module_test.bzl deleted file mode 100644 index 46092b9..0000000 --- a/bazel/rules/score_module/test/score_module_test.bzl +++ /dev/null @@ -1,187 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 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 -# ******************************************************************************* -"""Unit tests for the safety_element_out_of_context macro.""" - -load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") -load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo") - -# Test that the macro generates the expected targets -def _macro_generates_targets_test_impl(ctx): - """Test that safety_element_out_of_context macro generates all expected targets.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # The main target should exist and provide the required providers - asserts.true( - env, - DefaultInfo in target_under_test, - "Main target should provide DefaultInfo", - ) - - asserts.true( - env, - SphinxDocsLibraryInfo in target_under_test, - "Main target should provide SphinxDocsLibraryInfo", - ) - - return analysistest.end(env) - -macro_generates_targets_test = analysistest.make(_macro_generates_targets_test_impl) - -# Test that the macro correctly creates the index library target -def _macro_index_lib_test_impl(ctx): - """Test that the macro creates the index library target.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - - # Verify that transitive documentation includes the index - transitive_list = sphinx_info.transitive.to_list() - asserts.true( - env, - len(transitive_list) > 0, - "Macro should generate documentation with index", - ) - - return analysistest.end(env) - -macro_index_lib_test = analysistest.make(_macro_index_lib_test_impl) - -# Test that the macro properly aggregates all documentation artifacts -def _macro_doc_aggregation_test_impl(ctx): - """Test that the macro aggregates all documentation artifacts.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - transitive_list = sphinx_info.transitive.to_list() - - # Should have documentation entries for: - # - index - # - assumptions_of_use - # - component_requirements - # - architectural_design - # - safety_analysis - # That's at least 5 entries (could be more with nested dependencies) - asserts.true( - env, - len(transitive_list) >= 5, - "Macro should aggregate all documentation artifacts (expected at least 5, got {})".format(len(transitive_list)), - ) - - return analysistest.end(env) - -macro_doc_aggregation_test = analysistest.make(_macro_doc_aggregation_test_impl) - -# Test that the macro correctly prefixes documentation paths -def _macro_path_structure_test_impl(ctx): - """Test that the macro creates correct documentation path structure.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - transitive_list = sphinx_info.transitive.to_list() - - # Extract the module name from the target label - module_name = target_under_test.label.name - - # Check that documentation paths follow the expected structure - found_correct_structure = False - for entry in transitive_list: - if "docs/safety_elements/" in entry.prefix and module_name in entry.prefix: - found_correct_structure = True - break - - asserts.true( - env, - found_correct_structure, - "Documentation paths should follow 'docs/safety_elements//' structure", - ) - - return analysistest.end(env) - -macro_path_structure_test = analysistest.make(_macro_path_structure_test_impl) - -# Test that the macro handles visibility correctly -def _macro_visibility_test_impl(ctx): - """Test that the macro correctly handles visibility attribute.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # If the target can be accessed in the test, visibility is working - asserts.true( - env, - target_under_test != None, - "Target should be accessible according to visibility settings", - ) - - return analysistest.end(env) - -macro_visibility_test = analysistest.make(_macro_visibility_test_impl) - -# Test suite setup function -def _test_safety_element_macro(): - """Creates test targets for the safety_element_out_of_context macro.""" - - # Test 1: Verify macro generates expected targets - macro_generates_targets_test( - name = "macro_generates_targets_test", - target_under_test = ":test_macro_seooc", - ) - - # Test 2: Verify index library generation - macro_index_lib_test( - name = "macro_index_lib_test", - target_under_test = ":test_macro_seooc", - ) - - # Test 3: Verify documentation aggregation - macro_doc_aggregation_test( - name = "macro_doc_aggregation_test", - target_under_test = ":test_macro_seooc", - ) - - # Test 4: Verify path structure - macro_path_structure_test( - name = "macro_path_structure_test", - target_under_test = ":test_macro_seooc", - ) - - # Test 5: Verify visibility handling - macro_visibility_test( - name = "macro_visibility_test", - target_under_test = ":test_macro_seooc", - ) - -def safety_element_macro_test_suite(name): - """Creates a test suite for the safety_element_out_of_context macro. - - Args: - name: The name of the test suite. - """ - _test_safety_element_macro() - - native.test_suite( - name = name, - tests = [ - ":macro_generates_targets_test", - ":macro_index_lib_test", - ":macro_doc_aggregation_test", - ":macro_path_structure_test", - ":macro_visibility_test", - ], - ) diff --git a/bazel/rules/score_module/test/seooc_test.bzl b/bazel/rules/score_module/test/seooc_test.bzl index a417fed..f090359 100644 --- a/bazel/rules/score_module/test/seooc_test.bzl +++ b/bazel/rules/score_module/test/seooc_test.bzl @@ -1,345 +1,136 @@ -# ******************************************************************************* -# Copyright (c) 2025 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 -# ******************************************************************************* -"""Unit tests for the seooc rule.""" +""" +Test suite for safety_element_out_of_context macro. + +Tests the SEooC (Safety Element out of Context) functionality including: +- Index generation with artifact references +- Integration with score_module +- Sphinx-needs cross-referencing +- HTML output generation +""" load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") -load("@rules_python//sphinxdocs/private:sphinx_docs_library_info.bzl", "SphinxDocsLibraryInfo") +load("//bazel/rules/score_module/private:score_module.bzl", "ScoreModuleInfo", "ScoreNeedsInfo") -# Test that the seooc rule creates the correct providers -def _seooc_providers_test_impl(ctx): - """Test that seooc rule provides DefaultInfo and SphinxDocsLibraryInfo.""" +def _seooc_index_generation_test_impl(ctx): + """Test that SEooC generates proper index.rst file.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) - # Check that DefaultInfo is provided - asserts.true( - env, - DefaultInfo in target_under_test, - "seooc rule should provide DefaultInfo", - ) + # Get the generated index file + files = target_under_test[DefaultInfo].files.to_list() - # Check that SphinxDocsLibraryInfo is provided + # Find index.rst in the output files + index_file = None + for f in files: + if f.basename == "index.rst": + index_file = f + break + + # Assert index file exists asserts.true( env, - SphinxDocsLibraryInfo in target_under_test, - "seooc rule should provide SphinxDocsLibraryInfo", + index_file != None, + "Expected index.rst to be generated by _generate_seooc_index rule", ) return analysistest.end(env) -seooc_providers_test = analysistest.make(_seooc_providers_test_impl) +seooc_index_generation_test = analysistest.make( + impl = _seooc_index_generation_test_impl, +) -# Test that the seooc rule correctly aggregates transitive documentation -def _seooc_transitive_docs_test_impl(ctx): - """Test that seooc rule correctly aggregates transitive documentation.""" +def _seooc_artifacts_copied_test_impl(ctx): + """Test that all SEooC artifacts are copied to output directory.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] + files = target_under_test[DefaultInfo].files.to_list() - # Check that transitive field is a depset - asserts.true( - env, - type(sphinx_info.transitive) == type(depset([])), - "SphinxDocsLibraryInfo.transitive should be a depset", - ) - - # Check that transitive documentation is aggregated - transitive_list = sphinx_info.transitive.to_list() - asserts.true( - env, - len(transitive_list) > 0, - "seooc should aggregate transitive documentation", - ) + # Expected artifact basenames + expected_artifacts = [ + "assumptions_of_use.rst", + "component_requirements.rst", + "architectural_design.rst", + "safety_analysis.rst", + ] - # Verify that each entry has required fields - for entry in transitive_list: + # Check each artifact exists + actual_basenames = [f.basename for f in files] + for artifact in expected_artifacts: asserts.true( env, - hasattr(entry, "strip_prefix"), - "Each transitive entry should have strip_prefix field", + artifact in actual_basenames, + "Expected artifact '{}' to be in output files".format(artifact), ) - asserts.true( - env, - hasattr(entry, "prefix"), - "Each transitive entry should have prefix field", - ) - asserts.true( - env, - hasattr(entry, "files"), - "Each transitive entry should have files field", - ) - - # Check that prefix either starts with the expected path or is empty (for index) - asserts.true( - env, - entry.prefix.startswith("docs/safety_elements/") or entry.prefix == "", - "Documentation prefix should start with 'docs/safety_elements/' or be empty for index", - ) - - return analysistest.end(env) - -seooc_transitive_docs_test = analysistest.make(_seooc_transitive_docs_test_impl) - -# Test that the seooc rule correctly handles mandatory and optional attributes -def _seooc_attributes_test_impl(ctx): - """Test that seooc rule correctly handles mandatory attributes.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - - # Verify that documentation files are present - transitive_list = sphinx_info.transitive.to_list() - - # There should be at least documentation from the index - asserts.true( - env, - len(transitive_list) >= 1, - "seooc should have at least index documentation", - ) - - return analysistest.end(env) - -seooc_attributes_test = analysistest.make(_seooc_attributes_test_impl) - -# Test that seooc properly prefixes paths with module name -def _seooc_path_prefixing_test_impl(ctx): - """Test that seooc rule correctly prefixes paths with module name.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - transitive_list = sphinx_info.transitive.to_list() - - # Extract the module name from the target label - module_name = target_under_test.label.name - - # Check that at least one entry has the correct prefix - found_correct_prefix = False - for entry in transitive_list: - if module_name in entry.prefix: - found_correct_prefix = True - break - - asserts.true( - env, - found_correct_prefix, - "At least one documentation entry should contain the module name in its prefix", - ) - - return analysistest.end(env) - -seooc_path_prefixing_test = analysistest.make(_seooc_path_prefixing_test_impl) - -# Test that seooc properly processes assumptions_of_use attribute -def _seooc_has_assumptions_test_impl(ctx): - """Test that seooc rule properly processes assumptions_of_use attribute.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - transitive_list = sphinx_info.transitive.to_list() - - # Verify that documentation is present (should include assumptions) - asserts.true( - env, - len(transitive_list) > 0, - "seooc should include assumptions_of_use documentation", - ) return analysistest.end(env) -seooc_has_assumptions_test = analysistest.make(_seooc_has_assumptions_test_impl) +seooc_artifacts_copied_test = analysistest.make( + impl = _seooc_artifacts_copied_test_impl, +) -# Test that seooc properly processes component_requirements attribute -def _seooc_has_requirements_test_impl(ctx): - """Test that seooc rule properly processes component_requirements attribute.""" +def _seooc_score_module_generated_test_impl(ctx): + """Test that SEooC generates score_module with HTML output.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - transitive_list = sphinx_info.transitive.to_list() - - # Verify that documentation is present (should include requirements) + # Check that ScoreModuleInfo provider exists asserts.true( env, - len(transitive_list) > 0, - "seooc should include component_requirements documentation", + ScoreModuleInfo in target_under_test, + "Expected SEooC to provide ScoreModuleInfo from score_module", ) return analysistest.end(env) -seooc_has_requirements_test = analysistest.make(_seooc_has_requirements_test_impl) +seooc_score_module_generated_test = analysistest.make( + impl = _seooc_score_module_generated_test_impl, +) -# Test that seooc properly processes architectural_design attribute -def _seooc_has_architecture_test_impl(ctx): - """Test that seooc rule properly processes architectural_design attribute.""" +def _seooc_needs_provider_test_impl(ctx): + """Test that SEooC generates needs provider for cross-referencing.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - transitive_list = sphinx_info.transitive.to_list() - - # Verify that documentation is present (should include architecture) + # Check that ScoreNeedsInfo provider exists asserts.true( env, - len(transitive_list) > 0, - "seooc should include architectural_design documentation", + ScoreNeedsInfo in target_under_test, + "Expected SEooC_needs to provide ScoreNeedsInfo", ) return analysistest.end(env) -seooc_has_architecture_test = analysistest.make(_seooc_has_architecture_test_impl) +seooc_needs_provider_test = analysistest.make( + impl = _seooc_needs_provider_test_impl, +) -# Test that seooc properly processes safety_analysis attribute -def _seooc_has_safety_test_impl(ctx): - """Test that seooc rule properly processes safety_analysis attribute.""" +def _seooc_description_test_impl(ctx): + """Test that SEooC includes description in generated index.rst.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - transitive_list = sphinx_info.transitive.to_list() + # Get the generated index file + files = target_under_test[DefaultInfo].files.to_list() - # Verify that documentation is present (should include safety analysis) - asserts.true( - env, - len(transitive_list) > 0, - "seooc should include safety_analysis documentation", - ) - - return analysistest.end(env) - -seooc_has_safety_test = analysistest.make(_seooc_has_safety_test_impl) - -# Test that seooc properly processes index attribute -def _seooc_has_index_test_impl(ctx): - """Test that seooc rule properly processes index attribute.""" - env = analysistest.begin(ctx) - target_under_test = analysistest.target_under_test(env) - - # Get the SphinxDocsLibraryInfo provider - sphinx_info = target_under_test[SphinxDocsLibraryInfo] - transitive_list = sphinx_info.transitive.to_list() + # Find index.rst + index_file = None + for f in files: + if f.basename == "index.rst": + index_file = f + break - # Verify that documentation is present (should include index) + # Note: We can't easily read file contents in analysis test, + # but we can verify the file exists. The description content + # would be validated through integration tests or manual inspection. asserts.true( env, - len(transitive_list) > 0, - "seooc should include index documentation", + index_file != None, + "Expected index.rst to exist for description validation", ) return analysistest.end(env) -seooc_has_index_test = analysistest.make(_seooc_has_index_test_impl) - -# Test suite setup function -def _test_seooc(): - """Creates test targets for the seooc rule.""" - - # Test 1: Verify providers - seooc_providers_test( - name = "seooc_providers_test", - target_under_test = ":test_seooc_minimal", - ) - - # Test 2: Verify transitive documentation aggregation - seooc_transitive_docs_test( - name = "seooc_transitive_docs_test", - target_under_test = ":test_seooc_minimal", - ) - - # Test 3: Verify attribute handling - seooc_attributes_test( - name = "seooc_attributes_test", - target_under_test = ":test_seooc_minimal", - ) - - # Test 4: Verify path prefixing - seooc_path_prefixing_test( - name = "seooc_path_prefixing_test", - target_under_test = ":test_seooc_minimal", - ) - - # Test with complete attributes - seooc_providers_test( - name = "seooc_providers_test_complete", - target_under_test = ":test_seooc_complete", - ) - - seooc_transitive_docs_test( - name = "seooc_transitive_docs_test_complete", - target_under_test = ":test_seooc_complete", - ) - - # Test 5: Verify assumptions_of_use attribute is processed - seooc_has_assumptions_test( - name = "seooc_has_assumptions_test", - target_under_test = ":test_seooc_complete", - ) - - # Test 6: Verify component_requirements attribute is processed - seooc_has_requirements_test( - name = "seooc_has_requirements_test", - target_under_test = ":test_seooc_complete", - ) - - # Test 7: Verify architectural_design attribute is processed - seooc_has_architecture_test( - name = "seooc_has_architecture_test", - target_under_test = ":test_seooc_complete", - ) - - # Test 8: Verify safety_analysis attribute is processed - seooc_has_safety_test( - name = "seooc_has_safety_test", - target_under_test = ":test_seooc_complete", - ) - - # Test 9: Verify index attribute is processed - seooc_has_index_test( - name = "seooc_has_index_test", - target_under_test = ":test_seooc_complete", - ) - -def seooc_test_suite(name): - """Creates a test suite for the seooc rule. - - Args: - name: The name of the test suite. - """ - _test_seooc() - - native.test_suite( - name = name, - tests = [ - ":seooc_providers_test", - ":seooc_transitive_docs_test", - ":seooc_attributes_test", - ":seooc_path_prefixing_test", - ":seooc_providers_test_complete", - ":seooc_transitive_docs_test_complete", - ":seooc_has_assumptions_test", - ":seooc_has_requirements_test", - ":seooc_has_architecture_test", - ":seooc_has_safety_test", - ":seooc_has_index_test", - ], - ) +seooc_description_test = analysistest.make( + impl = _seooc_description_test_impl, +) From 16b3bb4fab1775ff72a0555da504333b82ad72c9 Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Thu, 22 Jan 2026 12:45:02 +0100 Subject: [PATCH 3/9] Fix integration tests --- MODULE.bazel | 2 +- bazel/rules/score_module/BUILD | 7 +- bazel/rules/score_module/docs/index.rst | 133 ++++-------------- .../docs/score_module_overview.puml | 72 ++++++++++ .../{score_seooc.bzl => score_component.bzl} | 45 +++--- bazel/rules/score_module/private/seooc.bzl | 12 -- .../{score_module.bzl => sphinx_module.bzl} | 6 +- bazel/rules/score_module/score_module.bzl | 12 +- .../templates/seooc_index.template.rst | 2 +- bazel/rules/score_module/test/BUILD | 71 +++------- ...nalysis.rst => dependability_analysis.rst} | 0 .../test/html_generation_test.bzl | 12 +- .../test/score_module_providers_test.bzl | 10 +- bazel/rules/score_module/test/seooc_test.bzl | 18 +-- ...zelversion~000c6ff (Fix integration tests) | 1 + ...zelversion~000c6ff (Fix integration tests) | 1 + 16 files changed, 180 insertions(+), 224 deletions(-) create mode 100644 bazel/rules/score_module/docs/score_module_overview.puml rename bazel/rules/score_module/private/{score_seooc.bzl => score_component.bzl} (87%) delete mode 100644 bazel/rules/score_module/private/seooc.bzl rename bazel/rules/score_module/private/{score_module.bzl => sphinx_module.bzl} (98%) rename bazel/rules/score_module/test/fixtures/seooc_test/{safety_analysis.rst => dependability_analysis.rst} (100%) create mode 100644 cr_checker/tests/.bazelversion~000c6ff (Fix integration tests) create mode 100644 starpls/integration_tests/.bazelversion~000c6ff (Fix integration tests) diff --git a/MODULE.bazel b/MODULE.bazel index 60d314e..766145b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -99,7 +99,7 @@ use_repo(multitool, "yamlfmt_hub") bazel_dep(name = "score_docs_as_code", version = "2.2.0") git_override( module_name = "score_docs_as_code", - commit = "be61c922ef9c3bf70dc7d3a5a1cb0c14f6e95d20", + commit = "17cf66e3449f0aa9e9fe22fe3ab1fbbffad733cf", remote = "https://github.com/eclipse-score/docs-as-code.git", ) diff --git a/bazel/rules/score_module/BUILD b/bazel/rules/score_module/BUILD index 43cfb91..8647e3a 100644 --- a/bazel/rules/score_module/BUILD +++ b/bazel/rules/score_module/BUILD @@ -1,6 +1,6 @@ load( "//bazel/rules/score_module:score_module.bzl", - "score_module", + "sphinx_module", ) exports_files([ @@ -34,11 +34,12 @@ py_binary( ], ) -score_module( - name = "score_module", +sphinx_module( + name = "score_module_doc", srcs = glob( [ "docs/**/*.rst", + "docs/**/*.puml", ], allow_empty = True, ), diff --git a/bazel/rules/score_module/docs/index.rst b/bazel/rules/score_module/docs/index.rst index 7096439..e1a812f 100644 --- a/bazel/rules/score_module/docs/index.rst +++ b/bazel/rules/score_module/docs/index.rst @@ -11,98 +11,21 @@ This package provides Bazel build rules for defining and building SCORE document Overview -------- -The ``score_module`` package provides two complementary Bazel rules for structuring and documenting software modules: +The ``sphinx_module`` package provides two complementary Bazel rules for structuring and documenting software modules: -1. **score_module**: A generic documentation module rule that builds Sphinx-based HTML documentation from RST source files. Suitable for any type of documentation module. +1. **sphinx_module**: A generic documentation module rule that builds Sphinx-based HTML documentation from RST source files. Suitable for any type of documentation module. -2. **safety_element_out_of_context**: A specialized rule for Safety Elements out of Context (SEooC) that enforces documentation structure with standardized artifacts for assumptions of use, requirements, architecture, and safety analysis. +2. **score_component**: A specialized rule for Safety Elements out of Context (SEooC) that enforces documentation structure with standardized artifacts for assumptions of use, requirements, architecture, and safety analysis. Both rules support **cross-module dependencies** through the ``deps`` attribute, enabling automatic integration of external sphinx-needs references and HTML merging for comprehensive documentation sets. -.. uml:: - - @startuml - package "score_module Rules" { - component "score_module" as SM <> - component "safety_element_out_of_context" as SEooC <> - } - - ' score_module structure - artifact "RST Sources" as RST <> - artifact "index.rst" as IDX <> - artifact "conf.py" as CONF <> - - ' SEooC-specific artifacts - artifact "Assumptions of Use" as AoU <> - artifact "Component Requirements" as CR <> - artifact "Architecture Design" as AD <> - artifact "Safety Analysis" as SA <> - - ' Implementation artifacts - card "Implementation" as Impl <> - card "Test Suite" as Test <> - - ' Generated outputs - folder "HTML Output" as HTML { - artifact "index.html" as HTMLIDX - folder "dependencies" as DEPS - } - artifact "needs.json" as NEEDS <> - - ' Dependencies - component "Process Module" as PROC <> - component "Platform Module" as PLAT <> - - ' Relationships for score_module - SM *-- RST : contains - SM *-- IDX : has - SM -- CONF : may have - SM ..> PROC : deps - SM ..> PLAT : deps - SM --> HTML : generates - SM --> NEEDS : exports - - ' Relationships for SEooC - SEooC *-- AoU : requires - SEooC *-- CR : requires - SEooC *-- AD : requires - SEooC *-- SA : requires - SEooC *-- Impl : requires - SEooC *-- Test : requires - SEooC ..> PROC : deps - SEooC ..> PLAT : deps - SEooC --> HTML : generates - SEooC --> NEEDS : exports - SEooC ..> SM : delegates to - - ' Cross-module dependencies - SM ..> SM : can depend on - SEooC ..> SEooC : can depend on - SEooC ..> SM : can depend on - - DEPS -- PROC : merges - DEPS -- PLAT : merges - - note right of SEooC - SEooC automatically generates - index.rst and symlinks artifacts, - then delegates to score_module - for Sphinx build - end note - - note right of DEPS - Dependencies' HTML is merged - into the output, enabling - unified navigation - end note - - @enduml +.. uml:: score_module_overview.puml Rules and Macros ---------------- -score_module +sphinx_module ~~~~~~~~~~~~ **File:** ``score_module.bzl`` @@ -113,7 +36,7 @@ score_module .. code-block:: python - score_module( + sphinx_module( name = "my_documentation", srcs = glob(["docs/**/*.rst"]), index = "docs/index.rst", @@ -130,7 +53,7 @@ score_module - ``name``: The name of the documentation module - ``srcs``: List of RST source files for the documentation - ``index``: Path to the main index.rst file -- ``deps``: Optional list of other ``score_module`` or ``safety_element_out_of_context`` targets that this module depends on. Dependencies are automatically integrated for cross-referencing via sphinx-needs and their HTML is merged into the output. +- ``deps``: Optional list of other ``sphinx_module`` or ``score_component`` targets that this module depends on. Dependencies are automatically integrated for cross-referencing via sphinx-needs and their HTML is merged into the output. - ``sphinx``: Label to the Sphinx build binary (default: ``//bazel/rules/score_module:score_build``) - ``config``: Optional custom conf.py file. If not provided, a default configuration is generated. - ``visibility``: Bazel visibility specification @@ -147,7 +70,7 @@ score_module **Build Strategy** -The ``score_module`` rule implements a multi-phase build strategy to ensure proper dependency resolution and documentation integration: +The ``sphinx_module`` rule implements a multi-phase build strategy to ensure proper dependency resolution and documentation integration: **Phase 1: Generate Needs JSON** @@ -189,7 +112,7 @@ Each successful build produces: - ``/_html/``: Intermediate HTML (before merging) -safety_element_out_of_context +score_component ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **File:** ``score_module.bzl`` @@ -200,12 +123,12 @@ safety_element_out_of_context .. code-block:: python - safety_element_out_of_context( + score_component( name = "my_seooc", assumptions_of_use = ["docs/assumptions_of_use.rst"], component_requirements = ["docs/requirements.rst"], architectural_design = ["docs/architecture.rst"], - safety_analysis = ["docs/safety_analysis.rst"], + dependability_analysis = ["docs/dependability_analysis.rst"], deps = [ "@score_platform//:score_platform_module", "@score_process//:score_process_module", @@ -221,8 +144,8 @@ safety_element_out_of_context - ``assumptions_of_use``: List of labels to ``.rst`` or ``.md`` files containing Assumptions of Use documentation - ``component_requirements``: List of labels to ``.rst`` or ``.md`` files containing component requirements specification - ``architectural_design``: List of labels to ``.rst`` or ``.md`` files containing architectural design specification -- ``safety_analysis``: List of labels to ``.rst`` or ``.md`` files containing safety analysis documentation (FMEA, DFA, etc.) -- ``deps``: Optional list of other ``score_module`` or ``safety_element_out_of_context`` targets that this SEooC depends on. Dependencies enable cross-referencing between modules and merge their HTML documentation into the final output. +- ``dependability_analysis``: List of labels to ``.rst`` or ``.md`` files containing safety analysis documentation (FMEA, DFA, etc.) +- ``deps``: Optional list of other ``sphinx_module`` or ``score_component`` targets that this SEooC depends on. Dependencies enable cross-referencing between modules and merge their HTML documentation into the final output. - ``implementations``: List of labels to implementation targets (cc_library, cc_binary, etc.) that realize the component requirements - ``tests``: List of labels to test targets (cc_test, py_test, etc.) that verify the implementation against requirements - ``sphinx``: Label to the Sphinx build binary (default: ``//bazel/rules/score_module:score_build``) @@ -231,7 +154,7 @@ safety_element_out_of_context **Generated Targets:** - ``_seooc_index``: Internal target that generates index.rst and symlinks all artifact files -- ````: Main SEooC target (internally calls ``score_module``) producing HTML documentation +- ````: Main SEooC target (internally calls ``sphinx_module``) producing HTML documentation - ``_needs``: Sphinx-needs JSON file for cross-referencing **Implementation Details:** @@ -240,13 +163,13 @@ The macro automatically: - Generates an index.rst file with a toctree referencing all provided artifacts - Creates symlinks to artifact files (assumptions of use, requirements, architecture, safety analysis) for co-location with the generated index -- Delegates to ``score_module`` for actual Sphinx build and HTML generation +- Delegates to ``sphinx_module`` for actual Sphinx build and HTML generation - Integrates dependencies for cross-module referencing and HTML merging Dependency Management --------------------- -Both ``score_module`` and ``safety_element_out_of_context`` support cross-module dependencies through the ``deps`` attribute. This enables: +Both ``sphinx_module`` and ``score_component`` support cross-module dependencies through the ``deps`` attribute. This enables: **Cross-Referencing with Sphinx-Needs** @@ -279,12 +202,12 @@ This allows seamless navigation between related documentation modules while main # @score_platform//:score_platform_module # My component that depends on process and platform - safety_element_out_of_context( + score_component( name = "my_component_seooc", assumptions_of_use = ["docs/assumptions.rst"], component_requirements = ["docs/requirements.rst"], architectural_design = ["docs/architecture.rst"], - safety_analysis = ["docs/safety.rst"], + dependability_analysis = ["docs/safety.rst"], deps = [ "@score_process//:score_process_module", "@score_platform//:score_platform_module", @@ -294,7 +217,7 @@ This allows seamless navigation between related documentation modules while main Documentation Structure ----------------------- -**For safety_element_out_of_context:** +**For score_component:** The macro automatically generates an index.rst and organizes files:: @@ -303,9 +226,9 @@ The macro automatically generates an index.rst and organizes files:: ├── assumptions_of_use.rst # Symlinked artifact ├── component_requirements.rst # Symlinked artifact ├── architectural_design.rst # Symlinked artifact - └── safety_analysis.rst # Symlinked artifact + └── dependability_analysis.rst # Symlinked artifact -**For score_module:** +**For sphinx_module:** User provides the complete source structure:: @@ -349,7 +272,7 @@ The default configuration includes common SCORE extensions: **Custom Configuration** -For ``score_module``, provide a custom conf.py via the ``config`` attribute to override the default Sphinx configuration. +For ``sphinx_module``, provide a custom conf.py via the ``config`` attribute to override the default Sphinx configuration. Usage Examples @@ -360,9 +283,9 @@ Example 1: Generic Documentation Module .. code-block:: python - load("//bazel/rules/score_module:score_module.bzl", "score_module") + load("//bazel/rules/score_module:score_module.bzl", "sphinx_module") - score_module( + sphinx_module( name = "platform_docs", srcs = glob(["docs/**/*.rst"]), index = "docs/index.rst", @@ -384,7 +307,7 @@ Example 2: Safety Element out of Context .. code-block:: python load("//bazel/rules/score_module:score_module.bzl", - "safety_element_out_of_context") + "score_component") # Implementation cc_library( @@ -401,12 +324,12 @@ Example 2: Safety Element out of Context ) # SEooC with dependencies - safety_element_out_of_context( + score_component( name = "kvs_seooc", assumptions_of_use = ["docs/assumptions.rst"], component_requirements = ["docs/requirements.rst"], architectural_design = ["docs/architecture.rst"], - safety_analysis = ["docs/fmea.rst", "docs/dfa.rst"], + dependability_analysis = ["docs/fmea.rst", "docs/dfa.rst"], deps = [ "@score_platform//:score_platform_module", "@score_process//:score_process_module", @@ -428,7 +351,7 @@ Design Rationale These rules provide a structured approach to documentation by: -1. **Two-Tier Architecture**: Generic ``score_module`` for flexibility, specialized ``safety_element_out_of_context`` for safety-critical work +1. **Two-Tier Architecture**: Generic ``sphinx_module`` for flexibility, specialized ``score_component`` for safety-critical work 2. **Dependency Management**: Automatic cross-referencing and HTML merging across modules 3. **Standardization**: SEooC enforces consistent structure for safety documentation 4. **Traceability**: Sphinx-needs integration enables bidirectional traceability diff --git a/bazel/rules/score_module/docs/score_module_overview.puml b/bazel/rules/score_module/docs/score_module_overview.puml new file mode 100644 index 0000000..75aaa3a --- /dev/null +++ b/bazel/rules/score_module/docs/score_module_overview.puml @@ -0,0 +1,72 @@ +@startuml + +skinparam linetype ortho +skinparam artifact<> { + BackgroundColor LightGreen + BorderColor Green +} +skinparam component<> { + BackgroundColor LightBlue + BorderColor Blue +} + +component "sphinx_module" as SM <> + +component "bzlmod" as bzlmod <> +component "score_component" as score_component <> +component "score_unit" as score_unit <> + +' sphinx_module structure + +' module-specific artifacts +artifact "manuals" as score_manual <> +artifact "dependability_management" as dependability_management <> +artifact release_notes <> + + +' score_component-specific artifacts +artifact "Assumptions of Use" as assumptions_of_use <> +artifact "Component Requirements" as component_requirements <> +artifact "Architecture Design" as architectural_design <> +artifact "Dependability Analysis" as dependability_analysis <> +artifact "Checklists" as checklists <> +artifact "VerificationReport" as verification_report <> + +artifact "Detailed Design" as detailed_design <> + + +' Implementation artifacts +card "Implementation" as Impl <> +card "Test Suite" as Test <> +bzlmod *-- score_component +SM <|-up- score_component + +' Relationships for bzlmod +bzlmod *-- score_manual +bzlmod *-- dependability_management +bzlmod *-- release_notes +bzlmod *-- checklists +bzlmod *-- verification_report + + +' Relationships for score_component +score_component *-- assumptions_of_use +score_component *-- component_requirements +score_component *-- architectural_design +score_component *-- dependability_analysis +score_component *-- Impl +score_component *-- Test +score_component *-- score_unit +score_component *-- checklists +score_component *-- score_component +score_component --> verification_report : creates + + +score_unit *-- detailed_design + + + +' Cross-module dependencies +SM ..> SM : can depend on + +@enduml diff --git a/bazel/rules/score_module/private/score_seooc.bzl b/bazel/rules/score_module/private/score_component.bzl similarity index 87% rename from bazel/rules/score_module/private/score_seooc.bzl rename to bazel/rules/score_module/private/score_component.bzl index 6408c72..97d5261 100644 --- a/bazel/rules/score_module/private/score_seooc.bzl +++ b/bazel/rules/score_module/private/score_component.bzl @@ -19,13 +19,13 @@ following S-CORE process guidelines. A SEooC is a safety-related element develop independently of a specific vehicle project. """ -load("//bazel/rules/score_module/private:score_module.bzl", "score_module") +load("//bazel/rules/score_module/private:sphinx_module.bzl", "sphinx_module") # ============================================================================ # Private Rule Implementation # ============================================================================ -def _generate_seooc_index_impl(ctx): +def _software_component_index_impl(ctx): """Generate index.rst file with references to all SEooC artifacts. This rule creates a Sphinx index.rst file that includes references to all @@ -48,11 +48,12 @@ def _generate_seooc_index_impl(ctx): "assumptions_of_use": [], "component_requirements": [], "architectural_design": [], - "safety_analysis": [], + "dependability_analysis": [], + "checklists": [], } # Process each artifact type - for artifact_name in ["assumptions_of_use", "component_requirements", "architectural_design", "safety_analysis"]: + for artifact_name in artifacts_by_type: attr_list = getattr(ctx.attr, artifact_name) if attr_list: # For label_list attributes, iterate over each label @@ -97,7 +98,8 @@ def _generate_seooc_index_impl(ctx): "{assumptions_of_use}": "\n ".join(artifacts_by_type["assumptions_of_use"]), "{component_requirements}": "\n ".join(artifacts_by_type["component_requirements"]), "{architectural_design}": "\n ".join(artifacts_by_type["architectural_design"]), - "{safety_analysis}": "\n ".join(artifacts_by_type["safety_analysis"]), + "{dependability_analysis}": "\n ".join(artifacts_by_type["dependability_analysis"]), + "{checklists}": "\n ".join(artifacts_by_type["checklists"]), }, ) @@ -109,8 +111,8 @@ def _generate_seooc_index_impl(ctx): # Private Rule Definition # ============================================================================ -_generate_seooc_index = rule( - implementation = _generate_seooc_index_impl, +_software_component_index = rule( + implementation = _software_component_index_impl, doc = "Generates index.rst file with references to SEooC artifacts", attrs = { "module_name": attr.string( @@ -141,7 +143,12 @@ _generate_seooc_index = rule( mandatory = True, doc = "Architectural design specification as defined in the S-CORE process", ), - "safety_analysis": attr.label_list( + "dependability_analysis": attr.label_list( + allow_files = [".rst", ".md"], + mandatory = True, + doc = "Safety analysis documentation as defined in the S-CORE process", + ), + "checklists": attr.label_list( allow_files = [".rst", ".md"], mandatory = True, doc = "Safety analysis documentation as defined in the S-CORE process", @@ -153,13 +160,14 @@ _generate_seooc_index = rule( # Public Macro # ============================================================================ -def safety_element_out_of_context( +def score_component( name, assumptions_of_use, component_requirements, architectural_design, - safety_analysis, + dependability_analysis, description, + checklists = [], implementations = [], tests = [], deps = [], @@ -169,7 +177,7 @@ def safety_element_out_of_context( This macro creates a complete SEooC module with integrated documentation generation. It generates an index.rst file referencing all SEooC artifacts - and builds HTML documentation using the score_module infrastructure. + and builds HTML documentation using the sphinx_module infrastructure. A SEooC is a safety-related architectural element (e.g., a software component) that is developed independently of a specific vehicle project and can be @@ -187,7 +195,7 @@ def safety_element_out_of_context( architectural_design: Label to a .rst or .md file containing the architectural design specification, describing the software architecture and design decisions as defined in the S-CORE process. - safety_analysis: Label to a .rst or .md file containing the safety + dependability_analysis: Label to a .rst or .md file containing the safety analysis, including FMEA, FMEDA, FTA, or other safety analysis results as defined in the S-CORE process. description: String containing a high-level description of the SEooC @@ -201,19 +209,19 @@ def safety_element_out_of_context( tests: Optional list of labels to Bazel test targets (cc_test, py_test, etc.) that verify the implementation against requirements. Includes unit tests and integration tests as defined in the S-CORE process. - deps: Optional list of other score_module or SEooC targets this module + deps: Optional list of other sphinx_module or SEooC targets this module depends on. Cross-references will work automatically. sphinx: Label to sphinx build binary. Default: //bazel/rules/score_module:score_build visibility: Bazel visibility specification for the generated SEooC targets. Generated Targets: _seooc_index: Internal rule that generates index.rst and copies artifacts - : Main SEooC target (score_module) with HTML documentation + : Main SEooC target (sphinx_module) with HTML documentation _needs: Internal target for sphinx-needs JSON generation """ # Step 1: Generate index.rst and collect all artifacts - _generate_seooc_index( + _software_component_index( name = name + "_seooc_index", module_name = name, description = description, @@ -221,13 +229,14 @@ def safety_element_out_of_context( assumptions_of_use = assumptions_of_use, component_requirements = component_requirements, architectural_design = architectural_design, - safety_analysis = safety_analysis, + dependability_analysis = dependability_analysis, + checklists = checklists, visibility = ["//visibility:private"], ) - # Step 2: Create score_module using generated index and artifacts + # Step 2: Create sphinx_module using generated index and artifacts # The index file is part of the _seooc_index target outputs - score_module( + sphinx_module( name = name, srcs = [":" + name + "_seooc_index"], index = ":" + name + "_seooc_index", # Label to the target, not a path diff --git a/bazel/rules/score_module/private/seooc.bzl b/bazel/rules/score_module/private/seooc.bzl deleted file mode 100644 index e42e917..0000000 --- a/bazel/rules/score_module/private/seooc.bzl +++ /dev/null @@ -1,12 +0,0 @@ -""" -Backwards compatibility wrapper for safety_element_out_of_context macro. - -This file re-exports the macro from its new location in score_seooc.bzl. -It exists for backwards compatibility and can be removed once all references -are updated to use score_seooc.bzl directly. -""" - -load("//bazel/rules/score_module/private:score_seooc.bzl", _safety_element_out_of_context = "safety_element_out_of_context") - -# Re-export the macro for backwards compatibility -safety_element_out_of_context = _safety_element_out_of_context diff --git a/bazel/rules/score_module/private/score_module.bzl b/bazel/rules/score_module/private/sphinx_module.bzl similarity index 98% rename from bazel/rules/score_module/private/score_module.bzl rename to bazel/rules/score_module/private/sphinx_module.bzl index 8865098..f14f3f8 100644 --- a/bazel/rules/score_module/private/score_module.bzl +++ b/bazel/rules/score_module/private/sphinx_module.bzl @@ -67,7 +67,7 @@ sphinx_rule_attrs = { mandatory = True, ), "deps": attr.label_list( - doc = "List of other score_module targets this module depends on for intersphinx.", + doc = "List of other sphinx_module targets this module depends on for intersphinx.", ), "_config_template": attr.label( default = Label("//bazel/rules/score_module:templates/conf.template.py"), @@ -254,7 +254,7 @@ _score_html = rule( # Rule wrappers # ====================================================================================== -def score_module( +def sphinx_module( name, srcs, index, @@ -273,7 +273,7 @@ def score_module( srcs: List of source files (.rst, .md) with index file first index: Label to index.rst file config: Label to conf.py configuration file (optional, will be auto-generated if not provided) - deps: List of other score_module targets this module depends on + deps: List of other sphinx_module targets this module depends on sphinx: Label to sphinx build binary (default: :sphinx_build) visibility: Bazel visibility """ diff --git a/bazel/rules/score_module/score_module.bzl b/bazel/rules/score_module/score_module.bzl index df3d521..a949d48 100644 --- a/bazel/rules/score_module/score_module.bzl +++ b/bazel/rules/score_module/score_module.bzl @@ -1,13 +1,13 @@ load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_docs") load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library") load( - "//bazel/rules/score_module/private:score_module.bzl", - _score_module = "score_module", + "//bazel/rules/score_module/private:score_component.bzl", + _score_component = "score_component", ) load( - "//bazel/rules/score_module/private:seooc.bzl", - _safety_element_out_of_context = "safety_element_out_of_context", + "//bazel/rules/score_module/private:sphinx_module.bzl", + _sphinx_module = "sphinx_module", ) -score_module = _score_module -safety_element_out_of_context = _safety_element_out_of_context +sphinx_module = _sphinx_module +score_component = _score_component diff --git a/bazel/rules/score_module/templates/seooc_index.template.rst b/bazel/rules/score_module/templates/seooc_index.template.rst index 818fdc0..e4ae5c3 100644 --- a/bazel/rules/score_module/templates/seooc_index.template.rst +++ b/bazel/rules/score_module/templates/seooc_index.template.rst @@ -23,4 +23,4 @@ {architectural_design} {component_requirements} {assumptions_of_use} - {safety_analysis} + {dependability_analysis} diff --git a/bazel/rules/score_module/test/BUILD b/bazel/rules/score_module/test/BUILD index d6c94f0..b155955 100644 --- a/bazel/rules/score_module/test/BUILD +++ b/bazel/rules/score_module/test/BUILD @@ -10,27 +10,20 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("//bazel/rules/score_module:score_module.bzl", "safety_element_out_of_context", "score_module") +load("//bazel/rules/score_module:score_module.bzl", "score_component", "sphinx_module") load( ":html_generation_test.bzl", "html_merging_test", "module_dependencies_test", "needs_transitive_test", - "score_module_test_suite", -) -load( - ":score_module_providers_test.bzl", - "deps_html_merging_test", - "deps_needs_collection_test", - "score_module_providers_test_suite", - "score_needs_with_deps_test", + "sphinx_module_test_suite", ) load( ":seooc_test.bzl", "seooc_artifacts_copied_test", "seooc_index_generation_test", "seooc_needs_provider_test", - "seooc_score_module_generated_test", + "seooc_sphinx_module_generated_test", ) package(default_visibility = ["//visibility:public"]) @@ -42,14 +35,14 @@ package(default_visibility = ["//visibility:public"]) # Test 1: Multi-Module Aggregation # Dependency graph: module_a_lib -> module_b_lib -> module_c_lib # module_a_lib -> module_c_lib (also direct) -score_module( +sphinx_module( name = "module_c_lib", srcs = glob(["fixtures/module_c/*.rst"]), index = "fixtures/module_c/index.rst", sphinx = "//bazel/rules/score_module:score_build", ) -score_module( +sphinx_module( name = "module_b_lib", srcs = glob(["fixtures/module_b/*.rst"]), index = "fixtures/module_b/index.rst", @@ -57,7 +50,7 @@ score_module( deps = [":module_c_lib"], ) -score_module( +sphinx_module( name = "module_a_lib", srcs = glob(["fixtures/module_a/*.rst"]), index = "fixtures/module_a/index.rst", @@ -69,14 +62,14 @@ score_module( ) # Test 2: SEooC (Safety Element out of Context) Module -# Tests the safety_element_out_of_context macro with S-CORE process artifacts -safety_element_out_of_context( +# Tests the score_component macro with S-CORE process artifacts +score_component( name = "seooc_test_lib", architectural_design = ["fixtures/seooc_test/architectural_design.rst"], assumptions_of_use = ["fixtures/seooc_test/assumptions_of_use.rst"], component_requirements = ["fixtures/seooc_test/component_requirements.rst"], + dependability_analysis = ["fixtures/seooc_test/dependability_analysis.rst"], description = "Test SEooC module demonstrating S-CORE process compliance structure.", - safety_analysis = ["fixtures/seooc_test/safety_analysis.rst"], deps = [ ":module_c_lib", ], @@ -103,46 +96,19 @@ html_merging_test( target_under_test = ":module_a_lib", ) -# ============================================================================ -# Test Instantiations - Provider Tests -# ============================================================================ - -# ScoreNeedsInfo Tests -score_needs_with_deps_test( - name = "score_needs_with_deps_test", - target_under_test = ":module_a_lib_needs", -) - -# Dependency Tests -deps_html_merging_test( - name = "deps_html_merging_test", - target_under_test = ":module_a_lib", -) - -deps_needs_collection_test( - name = "deps_needs_collection_test", - target_under_test = ":module_a_lib_needs", -) - # ============================================================================ # SEooC-Specific Tests # ============================================================================ -# Test that index generation works correctly -seooc_index_generation_test( - name = "seooc_tests_index_generation", - target_under_test = ":seooc_test_lib_seooc_index", -) - # Test that all artifacts are copied seooc_artifacts_copied_test( name = "seooc_tests_artifacts_copied", target_under_test = ":seooc_test_lib_seooc_index", ) -# Test that score_module is generated with correct providers -seooc_score_module_generated_test( - name = "seooc_tests_score_module_generated", +# Test that sphinx_module is generated with correct providers +seooc_sphinx_module_generated_test( + name = "seooc_tests_sphinx_module_generated", target_under_test = ":seooc_test_lib", ) @@ -156,20 +122,16 @@ seooc_needs_provider_test( # Test Suites # ============================================================================ -# Main test suite combining all score_module tests -score_module_test_suite(name = "score_module_tests") - -# Provider-focused test suite -score_module_providers_test_suite(name = "provider_tests") +# Main test suite combining all sphinx_module tests +sphinx_module_test_suite(name = "sphinx_module_tests") # SEooC-focused test suite test_suite( name = "seooc_tests", tests = [ ":seooc_tests_artifacts_copied", - ":seooc_tests_index_generation", ":seooc_tests_needs_provider", - ":seooc_tests_score_module_generated", + ":seooc_tests_sphinx_module_generated", ], ) @@ -177,8 +139,7 @@ test_suite( test_suite( name = "all_tests", tests = [ - ":provider_tests", - ":score_module_tests", ":seooc_tests", + ":sphinx_module_tests", ], ) diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/safety_analysis.rst b/bazel/rules/score_module/test/fixtures/seooc_test/dependability_analysis.rst similarity index 100% rename from bazel/rules/score_module/test/fixtures/seooc_test/safety_analysis.rst rename to bazel/rules/score_module/test/fixtures/seooc_test/dependability_analysis.rst diff --git a/bazel/rules/score_module/test/html_generation_test.bzl b/bazel/rules/score_module/test/html_generation_test.bzl index 0a0b1ee..6aeb47e 100644 --- a/bazel/rules/score_module/test/html_generation_test.bzl +++ b/bazel/rules/score_module/test/html_generation_test.bzl @@ -10,17 +10,17 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -"""Test rules for score_module HTML generation and dependencies.""" +"""Test rules for sphinx_module HTML generation and dependencies.""" load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") -load("//bazel/rules/score_module/private:score_module.bzl", "ScoreModuleInfo", "ScoreNeedsInfo") +load("//bazel/rules/score_module/private:sphinx_module.bzl", "ScoreModuleInfo", "ScoreNeedsInfo") # ============================================================================ # Provider Tests # ============================================================================ def _providers_test_impl(ctx): - """Test that score_module provides the correct providers.""" + """Test that sphinx_module provides the correct providers.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) @@ -67,7 +67,7 @@ basic_html_generation_test = analysistest.make(_basic_html_generation_test_impl) # ============================================================================ def _needs_generation_test_impl(ctx): - """Test that score_module generates needs.json files.""" + """Test that sphinx_module generates needs.json files.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) @@ -199,8 +199,8 @@ explicit_config_test = analysistest.make(_explicit_config_test_impl) # Test Suite # ============================================================================ -def score_module_test_suite(name): - """Create a comprehensive test suite for score_module. +def sphinx_module_test_suite(name): + """Create a comprehensive test suite for sphinx_module. Tests cover: - Needs.json generation and transitive collection diff --git a/bazel/rules/score_module/test/score_module_providers_test.bzl b/bazel/rules/score_module/test/score_module_providers_test.bzl index 6344105..6fbbc9c 100644 --- a/bazel/rules/score_module/test/score_module_providers_test.bzl +++ b/bazel/rules/score_module/test/score_module_providers_test.bzl @@ -10,7 +10,7 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -"""Tests for score_module providers and two-phase build system.""" +"""Tests for sphinx_module providers and two-phase build system.""" load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") load("//bazel/rules/score_module/private:score_module.bzl", "ScoreModuleInfo", "ScoreNeedsInfo") @@ -19,7 +19,7 @@ load("//bazel/rules/score_module/private:score_module.bzl", "ScoreModuleInfo", " # ScoreModuleInfo Provider Tests # ============================================================================ -def _score_module_info_fields_test_impl(ctx): +def _sphinx_module_info_fields_test_impl(ctx): """Test that ScoreModuleInfo provides all required fields.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) @@ -47,7 +47,7 @@ def _score_module_info_fields_test_impl(ctx): return analysistest.end(env) -score_module_info_fields_test = analysistest.make(_score_module_info_fields_test_impl) +sphinx_module_info_fields_test = analysistest.make(_sphinx_module_info_fields_test_impl) # ============================================================================ # ScoreNeedsInfo Provider Tests @@ -299,8 +299,8 @@ deps_needs_collection_test = analysistest.make(_deps_needs_collection_test_impl) # Test Suite # ============================================================================ -def score_module_providers_test_suite(name): - """Create a test suite for score_module providers and build phases. +def sphinx_module_providers_test_suite(name): + """Create a test suite for sphinx_module providers and build phases. Tests cover: - Transitive needs.json collection diff --git a/bazel/rules/score_module/test/seooc_test.bzl b/bazel/rules/score_module/test/seooc_test.bzl index f090359..bc05ceb 100644 --- a/bazel/rules/score_module/test/seooc_test.bzl +++ b/bazel/rules/score_module/test/seooc_test.bzl @@ -1,15 +1,15 @@ """ -Test suite for safety_element_out_of_context macro. +Test suite for score_component macro. Tests the SEooC (Safety Element out of Context) functionality including: - Index generation with artifact references -- Integration with score_module +- Integration with sphinx_module - Sphinx-needs cross-referencing - HTML output generation """ load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") -load("//bazel/rules/score_module/private:score_module.bzl", "ScoreModuleInfo", "ScoreNeedsInfo") +load("//bazel/rules/score_module/private:sphinx_module.bzl", "ScoreModuleInfo", "ScoreNeedsInfo") def _seooc_index_generation_test_impl(ctx): """Test that SEooC generates proper index.rst file.""" @@ -51,7 +51,7 @@ def _seooc_artifacts_copied_test_impl(ctx): "assumptions_of_use.rst", "component_requirements.rst", "architectural_design.rst", - "safety_analysis.rst", + "dependability_analysis.rst", ] # Check each artifact exists @@ -69,8 +69,8 @@ seooc_artifacts_copied_test = analysistest.make( impl = _seooc_artifacts_copied_test_impl, ) -def _seooc_score_module_generated_test_impl(ctx): - """Test that SEooC generates score_module with HTML output.""" +def _seooc_sphinx_module_generated_test_impl(ctx): + """Test that SEooC generates sphinx_module with HTML output.""" env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) @@ -78,13 +78,13 @@ def _seooc_score_module_generated_test_impl(ctx): asserts.true( env, ScoreModuleInfo in target_under_test, - "Expected SEooC to provide ScoreModuleInfo from score_module", + "Expected SEooC to provide ScoreModuleInfo from sphinx_module", ) return analysistest.end(env) -seooc_score_module_generated_test = analysistest.make( - impl = _seooc_score_module_generated_test_impl, +seooc_sphinx_module_generated_test = analysistest.make( + impl = _seooc_sphinx_module_generated_test_impl, ) def _seooc_needs_provider_test_impl(ctx): diff --git a/cr_checker/tests/.bazelversion~000c6ff (Fix integration tests) b/cr_checker/tests/.bazelversion~000c6ff (Fix integration tests) new file mode 100644 index 0000000..2bf50aa --- /dev/null +++ b/cr_checker/tests/.bazelversion~000c6ff (Fix integration tests) @@ -0,0 +1 @@ +8.3.0 diff --git a/starpls/integration_tests/.bazelversion~000c6ff (Fix integration tests) b/starpls/integration_tests/.bazelversion~000c6ff (Fix integration tests) new file mode 100644 index 0000000..2bf50aa --- /dev/null +++ b/starpls/integration_tests/.bazelversion~000c6ff (Fix integration tests) @@ -0,0 +1 @@ +8.3.0 From ef733dc687696af171f0ff54b0c4f0306f8c181f Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Mon, 26 Jan 2026 11:21:37 +0100 Subject: [PATCH 4/9] Added artefact specific rules --- README.md | 8 +- bazel/rules/score_module/docs/index.rst | 42 +++-- .../private/architectural_design.bzl | 147 +++++++++++++++ .../private/assumptions_of_use.bzl | 146 +++++++++++++++ .../private/component_requirements.bzl | 146 +++++++++++++++ .../private/dependability_analysis.bzl | 176 ++++++++++++++++++ .../private/feature_requirements.bzl | 119 ++++++++++++ .../rules/score_module/private/providers.bzl | 37 ++++ .../score_module/private/safety_analysis.bzl | 175 +++++++++++++++++ .../score_module/private/score_component.bzl | 89 ++++++--- .../score_module/private/sphinx_module.bzl | 4 +- bazel/rules/score_module/score_module.bzl | 35 ++++ .../score_module/templates/conf.template.py | 24 ++- bazel/rules/score_module/test/BUILD | 73 +++++++- .../test/fixtures/seooc_test/dfa.rst | 149 +++++++++++++++ .../seooc_test/dynamic_architecture.rst | 66 +++++++ .../seooc_test/feature_requirements.rst | 48 +++++ .../seooc_test/static_architecture.rst | 45 +++++ bazel/rules/score_module/test/seooc_test.bzl | 9 +- ...zelversion~000c6ff (Fix integration tests) | 1 - ...zelversion~000c6ff (Fix integration tests) | 1 - 21 files changed, 1467 insertions(+), 73 deletions(-) create mode 100644 bazel/rules/score_module/private/architectural_design.bzl create mode 100644 bazel/rules/score_module/private/assumptions_of_use.bzl create mode 100644 bazel/rules/score_module/private/component_requirements.bzl create mode 100644 bazel/rules/score_module/private/dependability_analysis.bzl create mode 100644 bazel/rules/score_module/private/feature_requirements.bzl create mode 100644 bazel/rules/score_module/private/providers.bzl create mode 100644 bazel/rules/score_module/private/safety_analysis.bzl create mode 100644 bazel/rules/score_module/test/fixtures/seooc_test/dfa.rst create mode 100644 bazel/rules/score_module/test/fixtures/seooc_test/dynamic_architecture.rst create mode 100644 bazel/rules/score_module/test/fixtures/seooc_test/feature_requirements.rst create mode 100644 bazel/rules/score_module/test/fixtures/seooc_test/static_architecture.rst delete mode 100644 cr_checker/tests/.bazelversion~000c6ff (Fix integration tests) delete mode 100644 starpls/integration_tests/.bazelversion~000c6ff (Fix integration tests) diff --git a/README.md b/README.md index a2d6fc0..2524b0b 100644 --- a/README.md +++ b/README.md @@ -36,14 +36,15 @@ load("@score_tooling//:defs.bzl", "cli_tool") ## Upgrading from separate MODULES -If you are still using separate module imports and want to upgrade to the new version. +If you are still using separate module imports and want to upgrade to the new version. Here are two examples to showcase how to do this. ``` load("@score_python_basics//:defs.bzl", "score_py_pytest") => load("@score_tooling//:defs.bzl", "score_py_pytest") load("@score_cr_checker//:cr_checker.bzl", "copyright_checker") => load("@score_tooling//:defs.bzl", "copyright_checker") ``` -All things inside of 'tooling' can now be imported from `@score_tooling//:defs.bzl`. + +All things inside of 'tooling' can now be imported from `@score_tooling//:defs.bzl`. The available import targets are: - score_virtualenv @@ -55,6 +56,7 @@ The available import targets are: - setup_starpls ## Format the tooling repository -```bash + +```bash bazel run //:format.fix ``` diff --git a/bazel/rules/score_module/docs/index.rst b/bazel/rules/score_module/docs/index.rst index e1a812f..17d5354 100644 --- a/bazel/rules/score_module/docs/index.rst +++ b/bazel/rules/score_module/docs/index.rst @@ -124,11 +124,13 @@ score_component .. code-block:: python score_component( - name = "my_seooc", - assumptions_of_use = ["docs/assumptions_of_use.rst"], - component_requirements = ["docs/requirements.rst"], - architectural_design = ["docs/architecture.rst"], - dependability_analysis = ["docs/dependability_analysis.rst"], + name = "my_component", + description = "My safety component providing core functionality.", + assumptions_of_use = [":my_assumptions_of_use"], + component_requirements = [":my_component_requirements"], + architectural_design = [":my_architectural_design"], + dependability_analysis = [":my_dependability_analysis"], + checklists = ["docs/safety_checklist.rst"], deps = [ "@score_platform//:score_platform_module", "@score_process//:score_process_module", @@ -141,10 +143,12 @@ score_component **Parameters:** - ``name``: The name of the safety element module -- ``assumptions_of_use``: List of labels to ``.rst`` or ``.md`` files containing Assumptions of Use documentation -- ``component_requirements``: List of labels to ``.rst`` or ``.md`` files containing component requirements specification -- ``architectural_design``: List of labels to ``.rst`` or ``.md`` files containing architectural design specification -- ``dependability_analysis``: List of labels to ``.rst`` or ``.md`` files containing safety analysis documentation (FMEA, DFA, etc.) +- ``description``: String containing a high-level description of the SEooC component. This text appears at the beginning of the generated documentation, providing context about what the component does and its purpose. Supports RST formatting. +- ``assumptions_of_use``: List of labels to ``assumptions_of_use`` targets or raw ``.rst``/``.md`` files containing Assumptions of Use documentation +- ``component_requirements``: List of labels to ``component_requirements`` targets or raw ``.rst``/``.md`` files containing component requirements specification +- ``architectural_design``: List of labels to ``architectural_design`` targets or raw ``.rst``/``.md`` files containing architectural design specification +- ``dependability_analysis``: List of labels to ``dependability_analysis`` targets or raw ``.rst``/``.md`` files containing safety analysis documentation (FMEA, DFA, etc.) +- ``checklists``: Optional list of labels to ``.rst`` or ``.md`` files containing safety checklists and verification documents - ``deps``: Optional list of other ``sphinx_module`` or ``score_component`` targets that this SEooC depends on. Dependencies enable cross-referencing between modules and merge their HTML documentation into the final output. - ``implementations``: List of labels to implementation targets (cc_library, cc_binary, etc.) that realize the component requirements - ``tests``: List of labels to test targets (cc_test, py_test, etc.) that verify the implementation against requirements @@ -169,15 +173,8 @@ The macro automatically: Dependency Management --------------------- -Both ``sphinx_module`` and ``score_component`` support cross-module dependencies through the ``deps`` attribute. This enables: - -**Cross-Referencing with Sphinx-Needs** - -Dependencies are automatically configured for sphinx-needs external references, allowing documents to reference requirements, architecture elements, and other needs across module boundaries using the ``:need:`` role. - -**HTML Documentation Merging** - -When building a module with dependencies, the HTML output from all dependent modules is merged into a unified documentation tree. For example: +Both ``sphinx_module`` and ``score_component`` support cross-module dependencies through the ``deps`` attribute. +The generated html structure whill look as follows: .. code-block:: text @@ -357,3 +354,12 @@ These rules provide a structured approach to documentation by: 4. **Traceability**: Sphinx-needs integration enables bidirectional traceability 5. **Automation**: Index generation, symlinking, and configuration management are automatic 6. **Build System Integration**: Bazel ensures reproducible, cacheable documentation builds + +Reference Implementation +------------------------ + +For complete working examples of all rules and macros, see the test BUILD file: + +.. literalinclude:: ../test/BUILD + :language: python + :caption: test/BUILD - Complete usage examples diff --git a/bazel/rules/score_module/private/architectural_design.bzl b/bazel/rules/score_module/private/architectural_design.bzl new file mode 100644 index 0000000..f00f106 --- /dev/null +++ b/bazel/rules/score_module/private/architectural_design.bzl @@ -0,0 +1,147 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Architectural Design build rules for S-CORE projects. + +This module provides macros and rules for defining architectural design +documentation following S-CORE process guidelines. Architectural design +documents describe the software architecture including static and dynamic views. +""" + +load("//bazel/rules/score_module/private:providers.bzl", "SphinxSourcesInfo") + +# ============================================================================ +# Provider Definition +# ============================================================================ + +ArchitecturalDesignInfo = provider( + doc = "Provider for architectural design artifacts", + fields = { + "static": "Depset of static architecture diagram files (e.g., class diagrams, component diagrams)", + "dynamic": "Depset of dynamic architecture diagram files (e.g., sequence diagrams, activity diagrams)", + "name": "Name of the architectural design target", + }, +) + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _architectural_design_impl(ctx): + """Implementation for architectural_design rule. + + Collects architectural design artifacts including static and dynamic + diagrams and provides them through the ArchitecturalDesignInfo provider. + + Args: + ctx: Rule context + + Returns: + List of providers including DefaultInfo and ArchitecturalDesignInfo + """ + static_files = depset(ctx.files.static) + dynamic_files = depset(ctx.files.dynamic) + + # Combine all files for DefaultInfo + all_files = depset( + transitive = [static_files, dynamic_files], + ) + + return [ + DefaultInfo(files = all_files), + ArchitecturalDesignInfo( + static = static_files, + dynamic = dynamic_files, + name = ctx.label.name, + ), + SphinxSourcesInfo( + srcs = all_files, + transitive_srcs = all_files, + ), + ] + +# ============================================================================ +# Rule Definition +# ============================================================================ + +_architectural_design = rule( + implementation = _architectural_design_impl, + doc = "Collects architectural design documents and diagrams for S-CORE process compliance", + attrs = { + "static": attr.label_list( + allow_files = [".puml", ".plantuml", ".png", ".svg", ".rst", ".md"], + mandatory = False, + doc = "Static architecture diagrams (class diagrams, component diagrams, etc.)", + ), + "dynamic": attr.label_list( + allow_files = [".puml", ".plantuml", ".png", ".svg", ".rst", ".md"], + mandatory = False, + doc = "Dynamic architecture diagrams (sequence diagrams, activity diagrams, etc.)", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def architectural_design( + name, + static = [], + dynamic = [], + visibility = None): + """Define architectural design following S-CORE process guidelines. + + Architectural design documents describe the software architecture of a + component, including both static and dynamic views. Static views show + the structural organization (classes, components, modules), while dynamic + views show the behavioral aspects (sequences, activities, states). + + Args: + name: The name of the architectural design target. Used as the base + name for all generated targets. + static: Optional list of labels to diagram files (.puml, .plantuml, + .png, .svg) or documentation files (.rst, .md) containing static + architecture views such as class diagrams, component diagrams, + or package diagrams as defined in the S-CORE process. + dynamic: Optional list of labels to diagram files (.puml, .plantuml, + .png, .svg) or documentation files (.rst, .md) containing dynamic + architecture views such as sequence diagrams, activity diagrams, + or state diagrams as defined in the S-CORE process. + visibility: Bazel visibility specification for the generated targets. + + Generated Targets: + : Main architectural design target providing ArchitecturalDesignInfo + + Example: + ```starlark + architectural_design( + name = "my_architectural_design", + static = [ + "class_diagram.puml", + "component_diagram.puml", + ], + dynamic = [ + "sequence_diagram.puml", + "activity_diagram.puml", + ], + ) + ``` + """ + _architectural_design( + name = name, + static = static, + dynamic = dynamic, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/private/assumptions_of_use.bzl b/bazel/rules/score_module/private/assumptions_of_use.bzl new file mode 100644 index 0000000..9051285 --- /dev/null +++ b/bazel/rules/score_module/private/assumptions_of_use.bzl @@ -0,0 +1,146 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Assumptions of Use build rules for S-CORE projects. + +This module provides macros and rules for defining Assumptions of Use (AoU) +following S-CORE process guidelines. Assumptions of Use define the safety-relevant +operating conditions and constraints for a Safety Element out of Context (SEooC). +""" + +load("//bazel/rules/score_module/private:feature_requirements.bzl", "FeatureRequirementsInfo") +load("//bazel/rules/score_module/private:providers.bzl", "SphinxSourcesInfo") + +# ============================================================================ +# Provider Definition +# ============================================================================ + +AssumptionsOfUseInfo = provider( + doc = "Provider for assumptions of use artifacts", + fields = { + "srcs": "Depset of source files containing assumptions of use", + "feature_requirements": "List of FeatureRequirementsInfo providers this AoU traces to", + "name": "Name of the assumptions of use target", + }, +) + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _assumptions_of_use_impl(ctx): + """Implementation for assumptions_of_use rule. + + Collects assumptions of use source files and links them to their + parent feature requirements through providers. + + Args: + ctx: Rule context + + Returns: + List of providers including DefaultInfo and AssumptionsOfUseInfo + """ + srcs = depset(ctx.files.srcs) + + # Collect feature requirements providers + feature_reqs = [] + for feat_req in ctx.attr.feature_requirement: + if FeatureRequirementsInfo in feat_req: + feature_reqs.append(feat_req[FeatureRequirementsInfo]) + + # Collect transitive sphinx sources from feature requirements + transitive = [srcs] + for feat_req in ctx.attr.feature_requirement: + if SphinxSourcesInfo in feat_req: + transitive.append(feat_req[SphinxSourcesInfo].transitive_srcs) + + return [ + DefaultInfo(files = srcs), + AssumptionsOfUseInfo( + srcs = srcs, + feature_requirements = feature_reqs, + name = ctx.label.name, + ), + SphinxSourcesInfo( + srcs = srcs, + transitive_srcs = depset(transitive = transitive), + ), + ] + +# ============================================================================ +# Rule Definition +# ============================================================================ + +_assumptions_of_use = rule( + implementation = _assumptions_of_use_impl, + doc = "Collects Assumptions of Use documents with traceability to feature requirements", + attrs = { + "srcs": attr.label_list( + allow_files = [".rst", ".md", ".trlc"], + mandatory = True, + doc = "Source files containing Assumptions of Use specifications", + ), + "feature_requirement": attr.label_list( + providers = [FeatureRequirementsInfo], + mandatory = False, + doc = "List of feature_requirements targets that these Assumptions of Use trace to", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def assumptions_of_use( + name, + srcs, + feature_requirement = [], + visibility = None): + """Define Assumptions of Use following S-CORE process guidelines. + + Assumptions of Use (AoU) define the safety-relevant operating conditions + and constraints for a Safety Element out of Context (SEooC). They specify + the conditions under which the component is expected to operate safely + and the responsibilities of the integrator. + + Args: + name: The name of the assumptions of use target. Used as the base + name for all generated targets. + srcs: List of labels to .rst, .md, or .trlc files containing the + Assumptions of Use specifications as defined in the S-CORE + process. + feature_requirement: Optional list of labels to feature_requirements + targets that these Assumptions of Use relate to. Establishes + traceability as defined in the S-CORE process. + visibility: Bazel visibility specification for the generated targets. + + Generated Targets: + : Main assumptions of use target providing AssumptionsOfUseInfo + + Example: + ```starlark + assumptions_of_use( + name = "my_assumptions_of_use", + srcs = ["assumptions_of_use.rst"], + feature_requirement = [":my_feature_requirements"], + ) + ``` + """ + _assumptions_of_use( + name = name, + srcs = srcs, + feature_requirement = feature_requirement, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/private/component_requirements.bzl b/bazel/rules/score_module/private/component_requirements.bzl new file mode 100644 index 0000000..51cfd6f --- /dev/null +++ b/bazel/rules/score_module/private/component_requirements.bzl @@ -0,0 +1,146 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Component Requirements build rules for S-CORE projects. + +This module provides macros and rules for defining component requirements +following S-CORE process guidelines. Component requirements are derived from +feature requirements and define the specific requirements for a software component. +""" + +load("//bazel/rules/score_module/private:feature_requirements.bzl", "FeatureRequirementsInfo") +load("//bazel/rules/score_module/private:providers.bzl", "SphinxSourcesInfo") + +# ============================================================================ +# Provider Definition +# ============================================================================ + +ComponentRequirementsInfo = provider( + doc = "Provider for component requirements artifacts", + fields = { + "srcs": "Depset of source files containing component requirements", + "feature_requirements": "List of FeatureRequirementsInfo providers this component traces to", + "name": "Name of the component requirements target", + }, +) + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _component_requirements_impl(ctx): + """Implementation for component_requirements rule. + + Collects component requirements source files and links them to their + parent feature requirements through providers. + + Args: + ctx: Rule context + + Returns: + List of providers including DefaultInfo and ComponentRequirementsInfo + """ + srcs = depset(ctx.files.srcs) + + # Collect feature requirements providers + feature_reqs = [] + for feat_req in ctx.attr.feature_requirement: + if FeatureRequirementsInfo in feat_req: + feature_reqs.append(feat_req[FeatureRequirementsInfo]) + + # Collect transitive sphinx sources from feature requirements + transitive = [srcs] + for feat_req in ctx.attr.feature_requirement: + if SphinxSourcesInfo in feat_req: + transitive.append(feat_req[SphinxSourcesInfo].transitive_srcs) + + return [ + DefaultInfo(files = srcs), + ComponentRequirementsInfo( + srcs = srcs, + feature_requirements = feature_reqs, + name = ctx.label.name, + ), + SphinxSourcesInfo( + srcs = srcs, + transitive_srcs = depset(transitive = transitive), + ), + ] + +# ============================================================================ +# Rule Definition +# ============================================================================ + +_component_requirements = rule( + implementation = _component_requirements_impl, + doc = "Collects component requirements documents with traceability to feature requirements", + attrs = { + "srcs": attr.label_list( + allow_files = [".rst", ".md", ".trlc"], + mandatory = True, + doc = "Source files containing component requirements specifications", + ), + "feature_requirement": attr.label_list( + providers = [FeatureRequirementsInfo], + mandatory = False, + doc = "List of feature_requirements targets that these component requirements trace to", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def component_requirements( + name, + srcs, + feature_requirement = [], + visibility = None): + """Define component requirements following S-CORE process guidelines. + + Component requirements are derived from feature requirements and define + the specific functional and safety requirements for a software component. + They establish traceability from high-level features to component-level + specifications. + + Args: + name: The name of the component requirements target. Used as the base + name for all generated targets. + srcs: List of labels to .rst, .md, or .trlc files containing the + component requirements specifications as defined in the S-CORE + process. + feature_requirement: Optional list of labels to feature_requirements + targets that these component requirements trace to. Establishes + bidirectional traceability as defined in the S-CORE process. + visibility: Bazel visibility specification for the generated targets. + + Generated Targets: + : Main component requirements target providing ComponentRequirementsInfo + + Example: + ```starlark + component_requirements( + name = "my_component_requirements", + srcs = ["component_requirements.rst"], + feature_requirement = [":my_feature_requirements"], + ) + ``` + """ + _component_requirements( + name = name, + srcs = srcs, + feature_requirement = feature_requirement, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/private/dependability_analysis.bzl b/bazel/rules/score_module/private/dependability_analysis.bzl new file mode 100644 index 0000000..eda7883 --- /dev/null +++ b/bazel/rules/score_module/private/dependability_analysis.bzl @@ -0,0 +1,176 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Dependability Analysis build rules for S-CORE projects. + +This module provides macros and rules for defining dependability analysis +documentation following S-CORE process guidelines. Dependability analysis +combines safety analysis with dependent failure analysis (DFA) to provide +a comprehensive view of component reliability and safety. +""" + +load("//bazel/rules/score_module/private:architectural_design.bzl", "ArchitecturalDesignInfo") +load("//bazel/rules/score_module/private:providers.bzl", "SphinxSourcesInfo") +load("//bazel/rules/score_module/private:safety_analysis.bzl", "SafetyAnalysisInfo") + +# ============================================================================ +# Provider Definition +# ============================================================================ + +DependabilityAnalysisInfo = provider( + doc = "Provider for dependability analysis artifacts", + fields = { + "safety_analysis": "List of SafetyAnalysisInfo providers", + "dfa": "Depset of Dependent Failure Analysis documentation", + "arch_design": "ArchitecturalDesignInfo provider for linked architectural design", + "name": "Name of the dependability analysis target", + }, +) + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _dependability_analysis_impl(ctx): + """Implementation for dependability_analysis rule. + + Collects dependability analysis artifacts including safety analysis results + and dependent failure analysis, linking them to architectural design. + + Args: + ctx: Rule context + + Returns: + List of providers including DefaultInfo and DependabilityAnalysisInfo + """ + dfa_files = depset(ctx.files.dfa) + + # Collect safety analysis providers + safety_analysis_infos = [] + safety_analysis_files = [] + for sa in ctx.attr.safety_analysis: + if SafetyAnalysisInfo in sa: + safety_analysis_infos.append(sa[SafetyAnalysisInfo]) + safety_analysis_files.append(sa.files) + + # Get architectural design provider if available + arch_design_info = None + if ctx.attr.arch_design and ArchitecturalDesignInfo in ctx.attr.arch_design: + arch_design_info = ctx.attr.arch_design[ArchitecturalDesignInfo] + + # Combine all files for DefaultInfo + all_files = depset( + transitive = [dfa_files] + safety_analysis_files, + ) + + # Collect transitive sphinx sources from safety analysis and architectural design + transitive = [all_files] + for sa in ctx.attr.safety_analysis: + if SphinxSourcesInfo in sa: + transitive.append(sa[SphinxSourcesInfo].transitive_srcs) + if ctx.attr.arch_design and SphinxSourcesInfo in ctx.attr.arch_design: + transitive.append(ctx.attr.arch_design[SphinxSourcesInfo].transitive_srcs) + + return [ + DefaultInfo(files = all_files), + DependabilityAnalysisInfo( + safety_analysis = safety_analysis_infos, + dfa = dfa_files, + arch_design = arch_design_info, + name = ctx.label.name, + ), + SphinxSourcesInfo( + srcs = all_files, + transitive_srcs = depset(transitive = transitive), + ), + ] + +# ============================================================================ +# Rule Definition +# ============================================================================ + +_dependability_analysis = rule( + implementation = _dependability_analysis_impl, + doc = "Collects dependability analysis documents for S-CORE process compliance", + attrs = { + "safety_analysis": attr.label_list( + providers = [SafetyAnalysisInfo], + mandatory = False, + doc = "List of safety_analysis targets containing FMEA, FMEDA, FTA results", + ), + "dfa": attr.label_list( + allow_files = [".rst", ".md"], + mandatory = False, + doc = "Dependent Failure Analysis (DFA) documentation", + ), + "arch_design": attr.label( + providers = [ArchitecturalDesignInfo], + mandatory = False, + doc = "Reference to architectural_design target for traceability", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def dependability_analysis( + name, + safety_analysis = [], + dfa = [], + arch_design = None, + visibility = None): + """Define dependability analysis following S-CORE process guidelines. + + Dependability analysis provides a comprehensive view of component + reliability and safety by combining safety analysis results with + dependent failure analysis (DFA). It establishes traceability to + the architectural design for complete safety argumentation. + + Args: + name: The name of the dependability analysis target. Used as the base + name for all generated targets. + safety_analysis: Optional list of labels to safety_analysis targets + containing the results of FMEA, FMEDA, FTA, or other safety + analysis methods as defined in the S-CORE process. + dfa: Optional list of labels to .rst or .md files containing + Dependent Failure Analysis (DFA) documentation. DFA identifies + failures that could affect multiple components or functions + as defined in the S-CORE process. + arch_design: Optional label to an architectural_design target for + establishing traceability between dependability analysis and + the software architecture. + visibility: Bazel visibility specification for the generated targets. + + Generated Targets: + : Main dependability analysis target providing DependabilityAnalysisInfo + + Example: + ```starlark + dependability_analysis( + name = "my_dependability_analysis", + safety_analysis = [":my_safety_analysis"], + dfa = ["dependent_failure_analysis.rst"], + arch_design = ":my_architectural_design", + ) + ``` + """ + _dependability_analysis( + name = name, + safety_analysis = safety_analysis, + dfa = dfa, + arch_design = arch_design, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/private/feature_requirements.bzl b/bazel/rules/score_module/private/feature_requirements.bzl new file mode 100644 index 0000000..dbcf107 --- /dev/null +++ b/bazel/rules/score_module/private/feature_requirements.bzl @@ -0,0 +1,119 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Feature Requirements build rules for S-CORE projects. + +This module provides macros and rules for defining feature requirements +following S-CORE process guidelines. Feature requirements describe the +high-level features that a software component must implement. +""" + +load("//bazel/rules/score_module/private:providers.bzl", "SphinxSourcesInfo") + +# ============================================================================ +# Provider Definition +# ============================================================================ + +FeatureRequirementsInfo = provider( + doc = "Provider for feature requirements artifacts", + fields = { + "srcs": "Depset of source files containing feature requirements", + "name": "Name of the feature requirements target", + }, +) + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _feature_requirements_impl(ctx): + """Implementation for feature_requirements rule. + + Collects feature requirements source files and provides them through + the FeatureRequirementsInfo provider. + + Args: + ctx: Rule context + + Returns: + List of providers including DefaultInfo and FeatureRequirementsInfo + """ + srcs = depset(ctx.files.srcs) + + return [ + DefaultInfo(files = srcs), + FeatureRequirementsInfo( + srcs = srcs, + name = ctx.label.name, + ), + SphinxSourcesInfo( + srcs = srcs, + transitive_srcs = srcs, + ), + ] + +# ============================================================================ +# Rule Definition +# ============================================================================ + +_feature_requirements = rule( + implementation = _feature_requirements_impl, + doc = "Collects feature requirements documents for S-CORE process compliance", + attrs = { + "srcs": attr.label_list( + allow_files = [".rst", ".md", ".trlc"], + mandatory = True, + doc = "Source files containing feature requirements specifications", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def feature_requirements( + name, + srcs, + visibility = None): + """Define feature requirements following S-CORE process guidelines. + + Feature requirements describe the high-level features and capabilities + that a software component must implement. They serve as the top-level + requirements that drive component-level requirements. + + Args: + name: The name of the feature requirements target. Used as the base + name for all generated targets. + srcs: List of labels to .rst, .md, or .trlc files containing the + feature requirements specifications as defined in the S-CORE + process. + visibility: Bazel visibility specification for the generated targets. + + Generated Targets: + : Main feature requirements target providing FeatureRequirementsInfo + + Example: + ```starlark + feature_requirements( + name = "my_feature_requirements", + srcs = ["feature_requirements.rst"], + ) + ``` + """ + _feature_requirements( + name = name, + srcs = srcs, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/private/providers.bzl b/bazel/rules/score_module/private/providers.bzl new file mode 100644 index 0000000..bfbb938 --- /dev/null +++ b/bazel/rules/score_module/private/providers.bzl @@ -0,0 +1,37 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 providers for S-CORE documentation build rules. + +This module defines providers that are shared across multiple documentation +build rules to enable consistent Sphinx documentation generation. +""" + +# ============================================================================ +# Provider Definitions +# ============================================================================ + +SphinxSourcesInfo = provider( + doc = """Provider for Sphinx documentation source files. + + This provider aggregates all source files needed for Sphinx documentation + builds, including reStructuredText, Markdown, PlantUML diagrams, and + image files. Rules that produce documentation artifacts should provide + this to enable integration with sphinx_module and score_component. + """, + fields = { + "srcs": "Depset of source files for Sphinx documentation (.rst, .md, .puml, .plantuml, .svg, .png, etc.)", + "transitive_srcs": "Depset of transitive source files from dependencies", + }, +) diff --git a/bazel/rules/score_module/private/safety_analysis.bzl b/bazel/rules/score_module/private/safety_analysis.bzl new file mode 100644 index 0000000..3c52dd4 --- /dev/null +++ b/bazel/rules/score_module/private/safety_analysis.bzl @@ -0,0 +1,175 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +""" +Safety Analysis build rules for S-CORE projects. + +This module provides macros and rules for defining safety analysis documentation +following S-CORE process guidelines. Safety analysis includes failure mode analysis, +control measures, fault tree analysis, and other safety-related artifacts. +""" + +load("//bazel/rules/score_module/private:architectural_design.bzl", "ArchitecturalDesignInfo") +load("//bazel/rules/score_module/private:providers.bzl", "SphinxSourcesInfo") + +# ============================================================================ +# Provider Definition +# ============================================================================ + +SafetyAnalysisInfo = provider( + doc = "Provider for safety analysis artifacts", + fields = { + "controlmeasures": "Depset of control measures documentation or requirements", + "failuremodes": "Depset of failure modes documentation or requirements", + "fta": "Depset of Fault Tree Analysis diagrams", + "arch_design": "ArchitecturalDesignInfo provider for linked architectural design", + "name": "Name of the safety analysis target", + }, +) + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _safety_analysis_impl(ctx): + """Implementation for safety_analysis rule. + + Collects safety analysis artifacts including control measures, failure modes, + and fault tree analysis diagrams, linking them to architectural design. + + Args: + ctx: Rule context + + Returns: + List of providers including DefaultInfo and SafetyAnalysisInfo + """ + controlmeasures = depset(ctx.files.controlmeasures) + failuremodes = depset(ctx.files.failuremodes) + fta = depset(ctx.files.fta) + + # Get architectural design provider if available + arch_design_info = None + if ctx.attr.arch_design and ArchitecturalDesignInfo in ctx.attr.arch_design: + arch_design_info = ctx.attr.arch_design[ArchitecturalDesignInfo] + + # Combine all files for DefaultInfo + all_files = depset( + transitive = [controlmeasures, failuremodes, fta], + ) + + # Collect transitive sphinx sources from architectural design + transitive = [all_files] + if ctx.attr.arch_design and SphinxSourcesInfo in ctx.attr.arch_design: + transitive.append(ctx.attr.arch_design[SphinxSourcesInfo].transitive_srcs) + + return [ + DefaultInfo(files = all_files), + SafetyAnalysisInfo( + controlmeasures = controlmeasures, + failuremodes = failuremodes, + fta = fta, + arch_design = arch_design_info, + name = ctx.label.name, + ), + SphinxSourcesInfo( + srcs = all_files, + transitive_srcs = depset(transitive = transitive), + ), + ] + +# ============================================================================ +# Rule Definition +# ============================================================================ + +_safety_analysis = rule( + implementation = _safety_analysis_impl, + doc = "Collects safety analysis documents for S-CORE process compliance", + attrs = { + "controlmeasures": attr.label_list( + allow_files = [".rst", ".md", ".trlc"], + mandatory = False, + doc = "Control measures documentation or requirements targets (can be AoUs or requirements)", + ), + "failuremodes": attr.label_list( + allow_files = [".rst", ".md", ".trlc"], + mandatory = False, + doc = "Failure modes documentation or requirements targets", + ), + "fta": attr.label_list( + allow_files = [".puml", ".plantuml", ".png", ".svg"], + mandatory = False, + doc = "Fault Tree Analysis (FTA) diagrams", + ), + "arch_design": attr.label( + providers = [ArchitecturalDesignInfo], + mandatory = False, + doc = "Reference to architectural_design target for traceability", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def safety_analysis( + name, + controlmeasures = [], + failuremodes = [], + fta = [], + arch_design = None, + visibility = None): + """Define safety analysis following S-CORE process guidelines. + + Safety analysis documents the safety-related analysis of a component, + including failure mode and effects analysis (FMEA/FMEDA), fault tree + analysis (FTA), and control measures that mitigate identified risks. + + Args: + name: The name of the safety analysis target. Used as the base + name for all generated targets. + controlmeasures: Optional list of labels to documentation files or + requirements targets containing control measures that mitigate + identified failure modes. Can reference Assumptions of Use or + requirements as defined in the S-CORE process. + failuremodes: Optional list of labels to documentation files or + requirements targets containing identified failure modes as + defined in the S-CORE process. + fta: Optional list of labels to Fault Tree Analysis diagram files + (.puml, .plantuml, .png, .svg) as defined in the S-CORE process. + arch_design: Optional label to an architectural_design target for + establishing traceability between safety analysis and architecture. + visibility: Bazel visibility specification for the generated targets. + + Generated Targets: + : Main safety analysis target providing SafetyAnalysisInfo + + Example: + ```starlark + safety_analysis( + name = "my_safety_analysis", + controlmeasures = [":my_control_measures"], + failuremodes = [":my_failure_modes"], + fta = ["fault_tree.puml"], + arch_design = ":my_architectural_design", + ) + ``` + """ + _safety_analysis( + name = name, + controlmeasures = controlmeasures, + failuremodes = failuremodes, + fta = fta, + arch_design = arch_design, + visibility = visibility, + ) diff --git a/bazel/rules/score_module/private/score_component.bzl b/bazel/rules/score_module/private/score_component.bzl index 97d5261..8af88ac 100644 --- a/bazel/rules/score_module/private/score_component.bzl +++ b/bazel/rules/score_module/private/score_component.bzl @@ -19,12 +19,39 @@ following S-CORE process guidelines. A SEooC is a safety-related element develop independently of a specific vehicle project. """ +load("//bazel/rules/score_module/private:providers.bzl", "SphinxSourcesInfo") load("//bazel/rules/score_module/private:sphinx_module.bzl", "sphinx_module") # ============================================================================ # Private Rule Implementation # ============================================================================ +def _get_sphinx_files(target): + """Extract documentation files from a target. + + Handles both targets with SphinxSourcesInfo provider and raw file targets. + + Args: + target: A Bazel target that may provide SphinxSourcesInfo or raw files + + Returns: + List of files suitable for Sphinx documentation + """ + if SphinxSourcesInfo in target: + return target[SphinxSourcesInfo].srcs.to_list() + return target.files.to_list() + +def _filter_doc_files(files): + """Filter files to only include documentation files (.rst, .md). + + Args: + files: List of files to filter + + Returns: + List of documentation files + """ + return [f for f in files if f.extension in ["rst", "md"]] + def _software_component_index_impl(ctx): """Generate index.rst file with references to all SEooC artifacts. @@ -58,8 +85,13 @@ def _software_component_index_impl(ctx): if attr_list: # For label_list attributes, iterate over each label for label in attr_list: - files = label.files.to_list() - for artifact_file in files: + # Get files from SphinxSourcesInfo if available, otherwise use raw files + all_files = _get_sphinx_files(label) + + # Filter to only include documentation files for the index + doc_files = _filter_doc_files(all_files) + + for artifact_file in doc_files: # Check that artifact is not named index.rst if artifact_file.basename == "index.rst": fail("Error in {}: Artifact file '{}' in '{}' cannot be named 'index.rst' as this file is generated by the SEooC rule and would be overwritten.".format( @@ -123,35 +155,30 @@ _software_component_index = rule( mandatory = True, doc = "Description of the SEooC component that appears at the beginning of the documentation. Supports RST formatting.", ), - "template": attr.label( - allow_single_file = [".rst"], - mandatory = True, - doc = "Template file for generating index.rst", - ), "assumptions_of_use": attr.label_list( - allow_files = [".rst", ".md"], mandatory = True, - doc = "Assumptions of Use document as defined in the S-CORE process", + doc = "Assumptions of Use targets or files as defined in the S-CORE process. Can be assumptions_of_use targets or raw .rst/.md files.", ), "component_requirements": attr.label_list( - allow_files = [".rst", ".md"], mandatory = True, - doc = "Component requirements specification as defined in the S-CORE process", + doc = "Component requirements targets or files as defined in the S-CORE process. Can be component_requirements targets or raw .rst/.md files.", ), "architectural_design": attr.label_list( - allow_files = [".rst", ".md"], mandatory = True, - doc = "Architectural design specification as defined in the S-CORE process", + doc = "Architectural design targets or files as defined in the S-CORE process. Can be architectural_design targets or raw .rst/.md files.", ), "dependability_analysis": attr.label_list( - allow_files = [".rst", ".md"], mandatory = True, - doc = "Safety analysis documentation as defined in the S-CORE process", + doc = "Dependability analysis targets or files as defined in the S-CORE process. Can be dependability_analysis targets or raw .rst/.md files.", ), "checklists": attr.label_list( - allow_files = [".rst", ".md"], mandatory = True, - doc = "Safety analysis documentation as defined in the S-CORE process", + doc = "Safety checklists targets or files as defined in the S-CORE process.", + ), + "template": attr.label( + allow_single_file = [".rst"], + mandatory = True, + doc = "Template file for generating index.rst", ), }, ) @@ -186,22 +213,26 @@ def score_component( Args: name: The name of the safety element module. Used as the base name for all generated targets. - assumptions_of_use: Label to a .rst or .md file containing the Assumptions - of Use, which define the safety-relevant operating conditions and - constraints for the SEooC as defined in the S-CORE process. - component_requirements: Label to a .rst or .md file containing the - component requirements specification, defining functional and safety - requirements as defined in the S-CORE process. - architectural_design: Label to a .rst or .md file containing the - architectural design specification, describing the software - architecture and design decisions as defined in the S-CORE process. - dependability_analysis: Label to a .rst or .md file containing the safety - analysis, including FMEA, FMEDA, FTA, or other safety analysis - results as defined in the S-CORE process. + assumptions_of_use: List of labels to assumptions_of_use targets or raw + .rst/.md files containing the Assumptions of Use, which define the + safety-relevant operating conditions and constraints for the SEooC + as defined in the S-CORE process. + component_requirements: List of labels to component_requirements targets + or raw .rst/.md files containing the component requirements specification, + defining functional and safety requirements as defined in the S-CORE process. + architectural_design: List of labels to architectural_design targets or raw + .rst/.md files containing the architectural design specification, describing + the software architecture and design decisions as defined in the S-CORE process. + dependability_analysis: List of labels to dependability_analysis targets or raw + .rst/.md files containing the safety analysis, including FMEA, FMEDA, FTA, + or other safety analysis results as defined in the S-CORE process. description: String containing a high-level description of the SEooC component. This text appears at the beginning of the generated documentation, providing context about what the component does and its purpose. Supports RST formatting. + checklists: Optional list of labels to .rst or .md files containing + safety checklists and verification documents as defined in the + S-CORE process. implementations: Optional list of labels to Bazel targets representing the actual software implementation (cc_library, cc_binary, etc.) that realizes the component requirements. This is the source code diff --git a/bazel/rules/score_module/private/sphinx_module.bzl b/bazel/rules/score_module/private/sphinx_module.bzl index f14f3f8..124f47f 100644 --- a/bazel/rules/score_module/private/sphinx_module.bzl +++ b/bazel/rules/score_module/private/sphinx_module.bzl @@ -192,7 +192,7 @@ def _score_html_impl(ctx): inputs = html_inputs, outputs = [sphinx_html_output], arguments = html_args, - progress_message = "Building HTML with external needs: %s" % ctx.label.name, + progress_message = "Building HTML: %s" % ctx.label.name, executable = ctx.executable.sphinx, ) @@ -260,7 +260,7 @@ def sphinx_module( index, config = None, deps = [], - sphinx = "@//bazel/rules/score_module:score_build", + sphinx = Label("//bazel/rules/score_module:score_build"), visibility = ["//visibility:public"]): """Build a Sphinx module with transitive HTML dependencies. diff --git a/bazel/rules/score_module/score_module.bzl b/bazel/rules/score_module/score_module.bzl index a949d48..63cdcf3 100644 --- a/bazel/rules/score_module/score_module.bzl +++ b/bazel/rules/score_module/score_module.bzl @@ -1,5 +1,33 @@ load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_docs") load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library") +load( + "//bazel/rules/score_module/private:architectural_design.bzl", + _architectural_design = "architectural_design", +) +load( + "//bazel/rules/score_module/private:assumptions_of_use.bzl", + _assumptions_of_use = "assumptions_of_use", +) +load( + "//bazel/rules/score_module/private:component_requirements.bzl", + _component_requirements = "component_requirements", +) +load( + "//bazel/rules/score_module/private:dependability_analysis.bzl", + _dependability_analysis = "dependability_analysis", +) +load( + "//bazel/rules/score_module/private:feature_requirements.bzl", + _feature_requirements = "feature_requirements", +) +load( + "//bazel/rules/score_module/private:providers.bzl", + _SphinxSourcesInfo = "SphinxSourcesInfo", +) +load( + "//bazel/rules/score_module/private:safety_analysis.bzl", + _safety_analysis = "safety_analysis", +) load( "//bazel/rules/score_module/private:score_component.bzl", _score_component = "score_component", @@ -9,5 +37,12 @@ load( _sphinx_module = "sphinx_module", ) +architectural_design = _architectural_design +assumptions_of_use = _assumptions_of_use +component_requirements = _component_requirements +dependability_analysis = _dependability_analysis +feature_requirements = _feature_requirements +safety_analysis = _safety_analysis sphinx_module = _sphinx_module score_component = _score_component +SphinxSourcesInfo = _SphinxSourcesInfo diff --git a/bazel/rules/score_module/templates/conf.template.py b/bazel/rules/score_module/templates/conf.template.py index 6f0c188..3ce867d 100644 --- a/bazel/rules/score_module/templates/conf.template.py +++ b/bazel/rules/score_module/templates/conf.template.py @@ -137,9 +137,9 @@ def load_external_needs() -> List[Dict[str, Any]]: config["json_path"] = str(json_path) print(f"INFO: Added external needs config for '{key}':") - print(f" json_path: {config['json_path']}") - print(f" id_prefix: {config.get('id_prefix', 'none')}") - print(f" version: {config.get('version', 'none')}") + print(f"INFO: json_path: {config['json_path']}") + print(f"INFO: id_prefix: {config.get('id_prefix', 'none')}") + print(f"INFO: version: {config.get('version', 'none')}") external_needs.append(config) @@ -148,19 +148,27 @@ def load_external_needs() -> List[Dict[str, Any]]: def verify_config(app: Any, config: Any) -> None: """ - Verify that configuration was properly loaded. + Initialize and verify external needs configuration. This is called during Sphinx's config-inited event to ensure - external needs configuration is correctly set up. + external needs configuration is correctly set up. We need to + explicitly set the config value here because Sphinx doesn't + automatically pick up module-level variables for extension configs. Args: app: Sphinx application object config: Sphinx configuration object """ + # Set the config from our module-level variable + # This is needed because sphinx-needs registers its config with add_config_value + # which doesn't automatically pick up module-level variables from conf.py + if needs_external_needs: + config.needs_external_needs = needs_external_needs + print("=" * 80) print("INFO: Verifying Sphinx configuration") - print(f" Project: {config.project}") - print(f" External needs count: {len(config.needs_external_needs)}") + print(f"INFO: Project: {config.project}") + print(f"INFO: External needs count: {len(config.needs_external_needs)}") print("=" * 80) @@ -189,4 +197,6 @@ def setup(app: Any) -> Dict[str, Any]: print(f"INFO: Current working directory: {Path.cwd()}") # Load external needs configuration +# Note: This sets a module-level variable that is then applied to the Sphinx +# config object in the verify_config callback during the config-inited event needs_external_needs = load_external_needs() diff --git a/bazel/rules/score_module/test/BUILD b/bazel/rules/score_module/test/BUILD index b155955..489ba7d 100644 --- a/bazel/rules/score_module/test/BUILD +++ b/bazel/rules/score_module/test/BUILD @@ -10,7 +10,17 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("//bazel/rules/score_module:score_module.bzl", "score_component", "sphinx_module") +load( + "//bazel/rules/score_module:score_module.bzl", + "architectural_design", + "assumptions_of_use", + "component_requirements", + "dependability_analysis", + "feature_requirements", + "safety_analysis", + "score_component", + "sphinx_module", +) load( ":html_generation_test.bzl", "html_merging_test", @@ -21,7 +31,6 @@ load( load( ":seooc_test.bzl", "seooc_artifacts_copied_test", - "seooc_index_generation_test", "seooc_needs_provider_test", "seooc_sphinx_module_generated_test", ) @@ -63,15 +72,65 @@ sphinx_module( # Test 2: SEooC (Safety Element out of Context) Module # Tests the score_component macro with S-CORE process artifacts + +# - Feature Requirements: wp__requirements_feat +# TODO: Feature requirements are a stand-alone artifact for now +# We have to link them manually to component requirements +feature_requirements( + name = "feat_req", + srcs = ["fixtures/seooc_test/feature_requirements.rst"], +) + +# - Component Requirements: wp__requirements_comp +component_requirements( + name = "comp_req", + srcs = ["fixtures/seooc_test/component_requirements.rst"], + feature_requirement = [":feat_req"], +) + +# - Assumptions of Use: wp__requirements_comp_aou +assumptions_of_use( + name = "aous", + srcs = ["fixtures/seooc_test/assumptions_of_use.rst"], + feature_requirement = [":feat_req"], +) + +# - Architecture Design: wp__component_arch +architectural_design( + name = "arch_design", + dynamic = ["fixtures/seooc_test/dynamic_architecture.rst"], + static = ["fixtures/seooc_test/static_architecture.rst"], +) + +# - Safety Analysis (DFA): wp__sw_component_dfa +# - Safety Analysis (FMEA): wp__sw_component_fmea +dependability_analysis( + name = "dependability_analysis_target", + arch_design = ":arch_design", + dfa = ["fixtures/seooc_test/dfa.rst"], + safety_analysis = [":samplelibrary_safety_analysis"], +) + +safety_analysis( + name = "samplelibrary_safety_analysis", + # TODO + # controlmeasures = [], # can be AoUs or requirements + # failuremodes = [], + # fta = [], + arch_design = ":arch_design", +) + score_component( name = "seooc_test_lib", - architectural_design = ["fixtures/seooc_test/architectural_design.rst"], - assumptions_of_use = ["fixtures/seooc_test/assumptions_of_use.rst"], - component_requirements = ["fixtures/seooc_test/component_requirements.rst"], - dependability_analysis = ["fixtures/seooc_test/dependability_analysis.rst"], + architectural_design = [":arch_design"], + assumptions_of_use = [":aous"], + component_requirements = [":comp_req"], + dependability_analysis = [":dependability_analysis_target"], description = "Test SEooC module demonstrating S-CORE process compliance structure.", + implementations = [], + tests = [], deps = [ - ":module_c_lib", + ":module_c_lib", # dependency to other seoocs/score_components ], ) diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/dfa.rst b/bazel/rules/score_module/test/fixtures/seooc_test/dfa.rst new file mode 100644 index 0000000..7b2e30d --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/seooc_test/dfa.rst @@ -0,0 +1,149 @@ +.. + # ******************************************************************************* + # Copyright (c) 2025 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 + # ******************************************************************************* + +Dependent Failure Analysis (DFA) +================================ + +This document contains the Dependent Failure Analysis (DFA) for the test SEooC module, +following ISO 26262 requirements for analysis of dependent failures. + +Component DFA Overview +---------------------- + +The dependent failure analysis identifies and evaluates common cause failures, +cascading failures, and dependent failures that could affect the safety of the component. + +.. comp_saf_dfa:: Common Cause Failure Analysis + :id: comp_saf_dfa__seooc_test__common_cause_analysis + :status: valid + :tags: dfa, safety, seooc_test + :violates: comp_arc_sta__seooc_test__data_processing_engine + :failure_id: ccf_root + :failure_effect: Common cause failures affecting multiple safety mechanisms simultaneously + :mitigated_by: aou_req__seooc_test__controlled_environment + :sufficient: yes + + **Analysis Scope**: Identification of common cause failures + + **Initiators Analyzed**: + + * Environmental conditions (temperature, EMI, vibration) + * Power supply anomalies + * Manufacturing and design defects + * Maintenance-induced failures + + **Conclusion**: All identified common cause initiators have adequate mitigation measures. + +.. comp_saf_dfa:: Power Supply Dependency + :id: comp_saf_dfa__seooc_test__power_dependency + :status: valid + :tags: dfa, safety, seooc_test + :violates: comp_arc_sta__seooc_test__data_processing_engine + :failure_id: power_ccf + :failure_effect: Power supply failure affecting both main and redundant processing paths + :mitigated_by: aou_req__seooc_test__supply_voltage + :sufficient: yes + + **Dependent Failure**: Power supply failure + + **Affected Elements**: + + * Main processing unit + * Redundant calculation path + * Communication interface + + **Independence Measures**: + + * Voltage monitoring with independent reference + * Brownout detection circuit + * Defined safe state on power loss + + **Residual Risk**: Acceptable (< 1e-8 per hour) + +.. comp_saf_dfa:: Clock Source Dependency + :id: comp_saf_dfa__seooc_test__clock_dependency + :status: valid + :tags: dfa, safety, seooc_test + :violates: comp_arc_sta__seooc_test__data_processing_engine + :failure_id: clock_ccf + :failure_effect: Clock failure causing simultaneous malfunction of timing-dependent safety mechanisms + :mitigated_by: comp_req__seooc_test__fault_detection + :sufficient: yes + + **Dependent Failure**: Clock source failure + + **Affected Elements**: + + * Watchdog timer + * Communication timing + * Task scheduling + + **Independence Measures**: + + * Internal RC oscillator as backup + * Clock monitoring unit + * Frequency range checks + + **Residual Risk**: Acceptable (< 5e-9 per hour) + +.. comp_saf_dfa:: Software Design Dependency + :id: comp_saf_dfa__seooc_test__sw_design_dependency + :status: valid + :tags: dfa, safety, seooc_test + :violates: comp_arc_sta__seooc_test__data_processing_engine + :failure_id: sw_ccf + :failure_effect: Systematic software defect in common code base affecting both calculation paths + :mitigated_by: comp_req__seooc_test__redundant_calculation + :sufficient: yes + + **Dependent Failure**: Systematic software defect + + **Affected Elements**: + + * Main calculation algorithm + * Redundant calculation algorithm + * Result comparison logic + + **Independence Measures**: + + * Diverse implementation of redundant path + * Independent development teams + * Different compilers/toolchains for each path + + **Residual Risk**: Acceptable (< 1e-7 per hour with diversity measures) + +DFA Summary +----------- + +.. comp_saf_dfa:: DFA Summary and Conclusion + :id: comp_saf_dfa__seooc_test__dfa_summary + :status: valid + :tags: dfa, safety, seooc_test, summary + :violates: comp_arc_sta__seooc_test__data_processing_engine + :failure_id: dfa_summary + :failure_effect: Combined dependent failure probability assessment + :mitigated_by: aou_req__seooc_test__controlled_environment + :sufficient: yes + + **Total Dependent Failure Probability**: < 1.5e-7 per hour + + **ASIL-B Target for Dependent Failures**: < 1e-6 per hour + + **Margin**: 6.7x + + **Status**: ✓ PASSED + + **Conclusion**: The component design provides adequate independence between + safety mechanisms. All identified dependent failure modes have been analyzed + and appropriate mitigation measures are in place. diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/dynamic_architecture.rst b/bazel/rules/score_module/test/fixtures/seooc_test/dynamic_architecture.rst new file mode 100644 index 0000000..33cf03f --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/seooc_test/dynamic_architecture.rst @@ -0,0 +1,66 @@ +.. + # ******************************************************************************* + # Copyright (c) 2025 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 + # ******************************************************************************* + +Dynamic Architecture +==================== + +This file contains the dynamic architectural design for the SEooC test component. + +.. comp_arc_dyn:: Data Processing Sequence + :id: comp_arc_dyn__seooc_test__data_processing + :security: NO + :safety: QM + :status: valid + :fulfils: comp_req__seooc_test__input_data_processing + + Sequence diagram showing the data processing flow from input to output. + + .. uml:: + + @startuml + participant "Client" as client + participant "SEooC Test Component" as main + participant "Data Processor" as processor + + client -> main : processData(input) + main -> processor : process(input) + processor --> main : result + main --> client : output + @enduml + +.. comp_arc_dyn:: Fault Handling Sequence + :id: comp_arc_dyn__seooc_test__fault_handling + :security: NO + :safety: ASIL_B + :status: valid + :fulfils: comp_req__seooc_test__fault_detection + + Sequence diagram showing the fault detection and safe state transition. + + .. uml:: + + @startuml + participant "Main Component" as main + participant "Fault Handler" as fault + participant "Safe State Manager" as safe + + main -> fault : checkHealth() + alt fault detected + fault -> safe : transitionToSafeState() + safe --> fault : safeStateConfirmed + fault --> main : faultHandled + else no fault + fault --> main : healthOK + end + @enduml diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/feature_requirements.rst b/bazel/rules/score_module/test/fixtures/seooc_test/feature_requirements.rst new file mode 100644 index 0000000..d1be18a --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/seooc_test/feature_requirements.rst @@ -0,0 +1,48 @@ +.. + # ******************************************************************************* + # Copyright (c) 2025 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 + # ******************************************************************************* + +Feature Requirements +==================== + +This file contains the feature requirements for the SEooC test module. + +.. feat_req:: Data Processing + :id: feat_req__seooc_test__data_processing + :reqtype: Functional + :security: NO + :safety: QM + :satisfies: stkh_req__platform__data_handling + :status: valid + + The SEooC test component shall process input data and provide processed output. + +.. feat_req:: Safe State Management + :id: feat_req__seooc_test__safe_state + :reqtype: Functional + :security: NO + :safety: ASIL_B + :satisfies: stkh_req__platform__safety + :status: valid + + The SEooC test component shall transition to a safe state upon detection of a fault condition. + +.. feat_req:: CAN Communication + :id: feat_req__seooc_test__can_comm + :reqtype: Interface + :security: NO + :safety: QM + :satisfies: stkh_req__platform__communication + :status: valid + + The SEooC test component shall support CAN message transmission and reception. diff --git a/bazel/rules/score_module/test/fixtures/seooc_test/static_architecture.rst b/bazel/rules/score_module/test/fixtures/seooc_test/static_architecture.rst new file mode 100644 index 0000000..b81321c --- /dev/null +++ b/bazel/rules/score_module/test/fixtures/seooc_test/static_architecture.rst @@ -0,0 +1,45 @@ +.. + # ******************************************************************************* + # Copyright (c) 2025 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 + # ******************************************************************************* + +Static Architecture +=================== + +This file contains the static architectural design for the SEooC test component. + +.. comp_arc_sta:: SEooC Test Component + :id: comp_arc_sta__seooc_test__main + :security: NO + :safety: QM + :status: valid + :fulfils: comp_req__seooc_test__input_data_processing + + The main component of the SEooC test module providing data processing capabilities. + +.. comp_arc_sta:: Data Processor + :id: comp_arc_sta__seooc_test__data_processor + :security: NO + :safety: QM + :status: valid + :fulfils: comp_req__seooc_test__output_accuracy + + Sub-component responsible for processing input data and generating output. + +.. comp_arc_sta:: Fault Handler + :id: comp_arc_sta__seooc_test__fault_handler + :security: NO + :safety: ASIL_B + :status: valid + :fulfils: comp_req__seooc_test__fault_detection + + Sub-component responsible for detecting and handling fault conditions. diff --git a/bazel/rules/score_module/test/seooc_test.bzl b/bazel/rules/score_module/test/seooc_test.bzl index bc05ceb..19872f2 100644 --- a/bazel/rules/score_module/test/seooc_test.bzl +++ b/bazel/rules/score_module/test/seooc_test.bzl @@ -46,12 +46,11 @@ def _seooc_artifacts_copied_test_impl(ctx): files = target_under_test[DefaultInfo].files.to_list() - # Expected artifact basenames + # Expected artifact basenames - these come from the SphinxSourcesInfo providers + # and are filtered to only include .rst/.md files for the index expected_artifacts = [ - "assumptions_of_use.rst", - "component_requirements.rst", - "architectural_design.rst", - "dependability_analysis.rst", + "component_requirements.rst", # from both :comp_req and :aous (same fixture file) + "dfa.rst", # from :dependability_analysis_target ] # Check each artifact exists diff --git a/cr_checker/tests/.bazelversion~000c6ff (Fix integration tests) b/cr_checker/tests/.bazelversion~000c6ff (Fix integration tests) deleted file mode 100644 index 2bf50aa..0000000 --- a/cr_checker/tests/.bazelversion~000c6ff (Fix integration tests) +++ /dev/null @@ -1 +0,0 @@ -8.3.0 diff --git a/starpls/integration_tests/.bazelversion~000c6ff (Fix integration tests) b/starpls/integration_tests/.bazelversion~000c6ff (Fix integration tests) deleted file mode 100644 index 2bf50aa..0000000 --- a/starpls/integration_tests/.bazelversion~000c6ff (Fix integration tests) +++ /dev/null @@ -1 +0,0 @@ -8.3.0 From 3279bb7ab69a519630263f4b0d8cbc5ace038ceb Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Tue, 27 Jan 2026 17:38:51 +0100 Subject: [PATCH 5/9] Add functionality to filter sphinx output --- .../rules/score_module/src/sphinx_wrapper.py | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/bazel/rules/score_module/src/sphinx_wrapper.py b/bazel/rules/score_module/src/sphinx_wrapper.py index d00e0b2..f648529 100644 --- a/bazel/rules/score_module/src/sphinx_wrapper.py +++ b/bazel/rules/score_module/src/sphinx_wrapper.py @@ -13,6 +13,9 @@ import time from pathlib import Path from typing import List, Optional +import re +import sys +from contextlib import redirect_stdout, redirect_stderr from sphinx.cmd.build import main as sphinx_main @@ -28,6 +31,24 @@ ) logger = logging.getLogger(__name__) +SANDBOX_PATH = re.compile(r'^.*_main/') +class StdoutProcessor: + def write(self, text): + if text.strip(): + text = re.sub(SANDBOX_PATH, '', text) + sys.__stdout__.write(f"[SPHINX_STDOUT]: {text.strip()}\n") + def flush(self): + sys.__stdout__.flush() + +class StderrProcessor: + def write(self, text): + if text.strip(): + text = re.sub(SANDBOX_PATH, '', text) + sys.__stderr__.write(f"[SPHINX_STDERR]: {text.strip()}\n") + def flush(self): + sys.__stderr__.flush() + + def get_env(name: str, required: bool = True) -> Optional[str]: """ @@ -216,8 +237,14 @@ def main() -> int: try: args = parse_arguments() validate_arguments(args) - sphinx_args = build_sphinx_arguments(args) - exit_code = run_sphinx_build(sphinx_args, args.builder) + # Create processor instance + stdout_processor = StdoutProcessor() + stderr_processor = StderrProcessor() + # Redirect stdout and stderr + with redirect_stderr(stdout_processor), redirect_stdout(stderr_processor): + sphinx_args = build_sphinx_arguments(args) + exit_code = run_sphinx_build(sphinx_args, args.builder) + exit_code = 0 return exit_code except ValueError as e: logger.error(f"Validation error: {e}") From 64170dd9ed2b2da5047cf9bcaeab894542a36936 Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Tue, 27 Jan 2026 18:02:23 +0100 Subject: [PATCH 6/9] switched from print to logger --- .../score_module/templates/conf.template.py | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/bazel/rules/score_module/templates/conf.template.py b/bazel/rules/score_module/templates/conf.template.py index 3ce867d..e916952 100644 --- a/bazel/rules/score_module/templates/conf.template.py +++ b/bazel/rules/score_module/templates/conf.template.py @@ -22,6 +22,11 @@ import os from pathlib import Path from typing import Any, Dict, List +from sphinx.util import logging + + +# Create a logger with the Sphinx namespace +logger = logging.getLogger(__name__) # Project configuration - {PROJECT_NAME} will be replaced by the module name during build project = "{PROJECT_NAME}" @@ -104,29 +109,29 @@ def load_external_needs() -> List[Dict[str, Any]]: needs_file = Path(NEEDS_EXTERNAL_FILE) if not needs_file.exists(): - print(f"INFO: {NEEDS_EXTERNAL_FILE} not found - no external dependencies") + logger.info(f"{NEEDS_EXTERNAL_FILE} not found - no external dependencies") return [] - print(f"INFO: Loading external needs from {NEEDS_EXTERNAL_FILE}") + logger.info(f"Loading external needs from {NEEDS_EXTERNAL_FILE}") try: with needs_file.open("r", encoding="utf-8") as file: needs_dict = json.load(file) except json.JSONDecodeError as e: - print(f"ERROR: Failed to parse {NEEDS_EXTERNAL_FILE}: {e}") + logger.error(f"Failed to parse {NEEDS_EXTERNAL_FILE}: {e}") return [] except Exception as e: - print(f"ERROR: Failed to read {NEEDS_EXTERNAL_FILE}: {e}") + logger.error(f"Failed to read {NEEDS_EXTERNAL_FILE}: {e}") return [] workspace_root = find_workspace_root() - print(f"INFO: Workspace root: {workspace_root}") + logger.info(f"Workspace root: {workspace_root}") external_needs = [] for key, config in needs_dict.items(): if "json_path" not in config: - print( - f"WARNING: External needs config for '{key}' missing 'json_path', skipping" + logger.warning( + f"External needs config for '{key}' missing 'json_path', skipping" ) continue @@ -136,10 +141,10 @@ def load_external_needs() -> List[Dict[str, Any]]: json_path = workspace_root / config["json_path"] config["json_path"] = str(json_path) - print(f"INFO: Added external needs config for '{key}':") - print(f"INFO: json_path: {config['json_path']}") - print(f"INFO: id_prefix: {config.get('id_prefix', 'none')}") - print(f"INFO: version: {config.get('version', 'none')}") + logger.info(f"Added external needs config for '{key}':") + logger.info(f" json_path: {config['json_path']}") + logger.info(f" id_prefix: {config.get('id_prefix', 'none')}") + logger.info(f" version: {config.get('version', 'none')}") external_needs.append(config) @@ -165,11 +170,11 @@ def verify_config(app: Any, config: Any) -> None: if needs_external_needs: config.needs_external_needs = needs_external_needs - print("=" * 80) - print("INFO: Verifying Sphinx configuration") - print(f"INFO: Project: {config.project}") - print(f"INFO: External needs count: {len(config.needs_external_needs)}") - print("=" * 80) + logger.info("=" * 80) + logger.info("Verifying Sphinx configuration") + logger.info(f" Project: {config.project}") + logger.info(f" External needs count: {len(config.needs_external_needs)}") + logger.info("=" * 80) def setup(app: Any) -> Dict[str, Any]: @@ -192,9 +197,9 @@ def setup(app: Any) -> Dict[str, Any]: # Initialize external needs configuration -print("=" * 80) -print(f"INFO: Sphinx configuration loaded for project: {project}") -print(f"INFO: Current working directory: {Path.cwd()}") +logger.info("=" * 80) +logger.info(f"Sphinx configuration loaded for project: {project}") +logger.info(f"Current working directory: {Path.cwd()}") # Load external needs configuration # Note: This sets a module-level variable that is then applied to the Sphinx From ec0d0fc47546234f47ef29813c00d20eb84ba163 Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Wed, 28 Jan 2026 11:30:34 +0100 Subject: [PATCH 7/9] Add fmea as attribute for dependability --- .../private/dependability_analysis.bzl | 17 ++++++++++++++++- .../score_module/private/score_component.bzl | 2 +- bazel/rules/score_module/src/sphinx_wrapper.py | 12 ++++++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/bazel/rules/score_module/private/dependability_analysis.bzl b/bazel/rules/score_module/private/dependability_analysis.bzl index eda7883..8c80503 100644 --- a/bazel/rules/score_module/private/dependability_analysis.bzl +++ b/bazel/rules/score_module/private/dependability_analysis.bzl @@ -33,6 +33,7 @@ DependabilityAnalysisInfo = provider( fields = { "safety_analysis": "List of SafetyAnalysisInfo providers", "dfa": "Depset of Dependent Failure Analysis documentation", + "fmea": "Depset of Failure Mode and Effects Analysis documentation", "arch_design": "ArchitecturalDesignInfo provider for linked architectural design", "name": "Name of the dependability analysis target", }, @@ -55,6 +56,7 @@ def _dependability_analysis_impl(ctx): List of providers including DefaultInfo and DependabilityAnalysisInfo """ dfa_files = depset(ctx.files.dfa) + fmea_files = depset(ctx.files.fmea) # Collect safety analysis providers safety_analysis_infos = [] @@ -71,7 +73,7 @@ def _dependability_analysis_impl(ctx): # Combine all files for DefaultInfo all_files = depset( - transitive = [dfa_files] + safety_analysis_files, + transitive = [dfa_files, fmea_files] + safety_analysis_files, ) # Collect transitive sphinx sources from safety analysis and architectural design @@ -87,6 +89,7 @@ def _dependability_analysis_impl(ctx): DependabilityAnalysisInfo( safety_analysis = safety_analysis_infos, dfa = dfa_files, + fmea = fmea_files, arch_design = arch_design_info, name = ctx.label.name, ), @@ -114,6 +117,11 @@ _dependability_analysis = rule( mandatory = False, doc = "Dependent Failure Analysis (DFA) documentation", ), + "fmea": attr.label_list( + allow_files = [".rst", ".md"], + mandatory = False, + doc = "Failure Mode and Effects Analysis (FMEA) documentation", + ), "arch_design": attr.label( providers = [ArchitecturalDesignInfo], mandatory = False, @@ -130,6 +138,7 @@ def dependability_analysis( name, safety_analysis = [], dfa = [], + fmea = [], arch_design = None, visibility = None): """Define dependability analysis following S-CORE process guidelines. @@ -149,6 +158,10 @@ def dependability_analysis( Dependent Failure Analysis (DFA) documentation. DFA identifies failures that could affect multiple components or functions as defined in the S-CORE process. + fmea: Optional list of labels to .rst or .md files containing + Failure Mode and Effects Analysis (FMEA) documentation. FMEA + identifies potential failure modes and their effects on the + system as defined in the S-CORE process. arch_design: Optional label to an architectural_design target for establishing traceability between dependability analysis and the software architecture. @@ -163,6 +176,7 @@ def dependability_analysis( name = "my_dependability_analysis", safety_analysis = [":my_safety_analysis"], dfa = ["dependent_failure_analysis.rst"], + fmea = ["failure_mode_effects_analysis.rst"], arch_design = ":my_architectural_design", ) ``` @@ -171,6 +185,7 @@ def dependability_analysis( name = name, safety_analysis = safety_analysis, dfa = dfa, + fmea = fmea, arch_design = arch_design, visibility = visibility, ) diff --git a/bazel/rules/score_module/private/score_component.bzl b/bazel/rules/score_module/private/score_component.bzl index 8af88ac..9096ef5 100644 --- a/bazel/rules/score_module/private/score_component.bzl +++ b/bazel/rules/score_module/private/score_component.bzl @@ -256,7 +256,7 @@ def score_component( name = name + "_seooc_index", module_name = name, description = description, - template = "//bazel/rules/score_module:templates/seooc_index.template.rst", + template = Label("//bazel/rules/score_module:templates/seooc_index.template.rst"), assumptions_of_use = assumptions_of_use, component_requirements = component_requirements, architectural_design = architectural_design, diff --git a/bazel/rules/score_module/src/sphinx_wrapper.py b/bazel/rules/score_module/src/sphinx_wrapper.py index f648529..1376057 100644 --- a/bazel/rules/score_module/src/sphinx_wrapper.py +++ b/bazel/rules/score_module/src/sphinx_wrapper.py @@ -31,25 +31,29 @@ ) logger = logging.getLogger(__name__) -SANDBOX_PATH = re.compile(r'^.*_main/') +SANDBOX_PATH = re.compile(r"^.*_main/") + + class StdoutProcessor: def write(self, text): if text.strip(): - text = re.sub(SANDBOX_PATH, '', text) + text = re.sub(SANDBOX_PATH, "", text) sys.__stdout__.write(f"[SPHINX_STDOUT]: {text.strip()}\n") + def flush(self): sys.__stdout__.flush() + class StderrProcessor: def write(self, text): if text.strip(): - text = re.sub(SANDBOX_PATH, '', text) + text = re.sub(SANDBOX_PATH, "", text) sys.__stderr__.write(f"[SPHINX_STDERR]: {text.strip()}\n") + def flush(self): sys.__stderr__.flush() - def get_env(name: str, required: bool = True) -> Optional[str]: """ Get an environment variable value. From b5b182dd9c1ed72b3f70939a72488d135645e5b5 Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Wed, 28 Jan 2026 17:50:59 +0100 Subject: [PATCH 8/9] Bugfix for relative references --- .../score_module/private/score_component.bzl | 262 ++++++++++++++---- .../templates/seooc_index.template.rst | 37 ++- 2 files changed, 237 insertions(+), 62 deletions(-) diff --git a/bazel/rules/score_module/private/score_component.bzl b/bazel/rules/score_module/private/score_component.bzl index 9096ef5..f3bddc0 100644 --- a/bazel/rules/score_module/private/score_component.bzl +++ b/bazel/rules/score_module/private/score_component.bzl @@ -27,30 +27,204 @@ load("//bazel/rules/score_module/private:sphinx_module.bzl", "sphinx_module") # ============================================================================ def _get_sphinx_files(target): - """Extract documentation files from a target. + return target[SphinxSourcesInfo].srcs.to_list() - Handles both targets with SphinxSourcesInfo provider and raw file targets. + +def _filter_doc_files(files): + """Filter files to only include documentation files. Args: - target: A Bazel target that may provide SphinxSourcesInfo or raw files + files: List of files to filter Returns: - List of files suitable for Sphinx documentation + List of documentation files """ - if SphinxSourcesInfo in target: - return target[SphinxSourcesInfo].srcs.to_list() - return target.files.to_list() + return [f for f in files if f.extension in ["rst", "md", "puml", "plantuml", "png", "svg"]] -def _filter_doc_files(files): - """Filter files to only include documentation files (.rst, .md). +def _find_common_directory(files): + """Find the longest common directory path for a list of files. Args: - files: List of files to filter + files: List of File objects Returns: - List of documentation files + String representing the common directory path, or empty string if none + """ + if not files: + return "" + + # Get all directory paths + dirs = [f.dirname for f in files] + + if not dirs: + return "" + + # Start with first directory + common = dirs[0] + + # Iterate through all directories to find common prefix + for d in dirs[1:]: + # Find common prefix between common and d + # Split into path components + common_parts = common.split("/") + d_parts = d.split("/") + + # Find matching prefix + new_common_parts = [] + for i in range(min(len(common_parts), len(d_parts))): + if common_parts[i] == d_parts[i]: + new_common_parts.append(common_parts[i]) + else: + break + + common = "/".join(new_common_parts) + + if not common: + break + + return common + +# ============================================================================ +# Path Computation and Validation Helpers +# ============================================================================ + +def _compute_relative_path(file, common_dir): + """Compute relative path from common directory to file. + + Args: + file: File object + common_dir: Common directory path string + + Returns: + String containing the relative path + """ + file_dir = file.dirname + + if not common_dir: + return file.basename + + if not file_dir.startswith(common_dir): + return file.basename + + if file_dir == common_dir: + return file.basename + + relative_subdir = file_dir[len(common_dir):].lstrip("/") + return relative_subdir + "/" + file.basename + +def _is_document_file(file): + """Check if file should be included in toctree. + + Args: + file: File object + + Returns: + Boolean indicating if file is a document (.rst or .md) + """ + return file.extension in ["rst", "md"] + +# ============================================================================ +# Artifact Processing Functions +# ============================================================================ + +def _create_artifact_symlink(ctx, artifact_name, artifact_file, relative_path): + """Create symlink for artifact file in output directory. + + Args: + ctx: Rule context + artifact_name: Name of artifact type (e.g., "architectural_design") + artifact_file: Source file + relative_path: Relative path within artifact directory + + Returns: + Declared output file + """ + output_file = ctx.actions.declare_file( + ctx.label.name + "/" + artifact_name + "/" + relative_path + ) + + ctx.actions.symlink( + output = output_file, + target_file = artifact_file, + ) + + return output_file + +def _process_artifact_files(ctx, artifact_name, label): + """Process all files from a single label for a given artifact type. + + Args: + ctx: Rule context + artifact_name: Name of artifact type + label: Label to process + + Returns: + Tuple of (output_files, index_references) + """ + output_files = [] + index_refs = [] + + # Get and filter files + all_files = _get_sphinx_files(label) + doc_files = _filter_doc_files(all_files) + + if not doc_files: + return (output_files, index_refs) + + # Find common directory to preserve hierarchy + common_dir = _find_common_directory(doc_files) + + # Process each file + for artifact_file in doc_files: + # Compute paths + relative_path = _compute_relative_path(artifact_file, common_dir) + + # Create symlink + output_file = _create_artifact_symlink( + ctx, + artifact_name, + artifact_file, + relative_path + ) + output_files.append(output_file) + + # Add to index if it's a document file + if _is_document_file(artifact_file): + doc_ref = (artifact_name + "/" + relative_path) \ + .replace(".rst", "") \ + .replace(".md", "") + index_refs.append(doc_ref) + + return (output_files, index_refs) + +def _process_artifact_type(ctx, artifact_name): + """Process all labels for a given artifact type. + + Args: + ctx: Rule context + artifact_name: Name of artifact type (e.g., "architectural_design") + + Returns: + Tuple of (output_files, index_references) """ - return [f for f in files if f.extension in ["rst", "md"]] + output_files = [] + index_refs = [] + + attr_list = getattr(ctx.attr, artifact_name) + if not attr_list: + return (output_files, index_refs) + + # Process each label + for label in attr_list: + label_outputs, label_refs = _process_artifact_files( + ctx, + artifact_name, + label + ) + output_files.extend(label_outputs) + index_refs.extend(label_refs) + + return (output_files, index_refs) def _software_component_index_impl(ctx): """Generate index.rst file with references to all SEooC artifacts. @@ -66,57 +240,27 @@ def _software_component_index_impl(ctx): DefaultInfo provider with generated index.rst file """ - # Declare output index.rst file + # Declare output index file index_rst = ctx.actions.declare_file(ctx.label.name + "/index.rst") - - # Collect all artifact files and create symlinks output_files = [index_rst] - artifacts_by_type = { - "assumptions_of_use": [], - "component_requirements": [], - "architectural_design": [], - "dependability_analysis": [], - "checklists": [], - } + + # Define artifact types to process + artifact_types = [ + "assumptions_of_use", + "component_requirements", + "architectural_design", + "dependability_analysis", + "checklists", + ] # Process each artifact type - for artifact_name in artifacts_by_type: - attr_list = getattr(ctx.attr, artifact_name) - if attr_list: - # For label_list attributes, iterate over each label - for label in attr_list: - # Get files from SphinxSourcesInfo if available, otherwise use raw files - all_files = _get_sphinx_files(label) - - # Filter to only include documentation files for the index - doc_files = _filter_doc_files(all_files) - - for artifact_file in doc_files: - # Check that artifact is not named index.rst - if artifact_file.basename == "index.rst": - fail("Error in {}: Artifact file '{}' in '{}' cannot be named 'index.rst' as this file is generated by the SEooC rule and would be overwritten.".format( - ctx.label, - artifact_file.path, - artifact_name, - )) - - # Create symlink in same directory as index - output_file = ctx.actions.declare_file( - ctx.label.name + "/" + artifact_file.basename, - ) - output_files.append(output_file) - - # Symlink instead of copying for better performance - ctx.actions.symlink( - output = output_file, - target_file = artifact_file, - ) - - # Add reference to index (without file extension) - doc_ref = artifact_file.basename.replace(".rst", "").replace(".md", "") - artifacts_by_type[artifact_name].append(doc_ref) - - # Substitute template variables (template handles indentation) + artifacts_by_type = {} + for artifact_name in artifact_types: + files, refs = _process_artifact_type(ctx, artifact_name) + output_files.extend(files) + artifacts_by_type[artifact_name] = refs + + # Generate index file from template title = ctx.attr.module_name underline = "=" * len(title) diff --git a/bazel/rules/score_module/templates/seooc_index.template.rst b/bazel/rules/score_module/templates/seooc_index.template.rst index e4ae5c3..7a0479f 100644 --- a/bazel/rules/score_module/templates/seooc_index.template.rst +++ b/bazel/rules/score_module/templates/seooc_index.template.rst @@ -16,11 +16,42 @@ {description} +Assumptions of Use +------------------ + .. toctree:: :maxdepth: 2 - :caption: Contents: - {architectural_design} - {component_requirements} {assumptions_of_use} + +Component Requirements +---------------------- + +.. toctree:: + :maxdepth: 2 + + {component_requirements} + +Architectural Design +-------------------- + +.. toctree:: + :maxdepth: 2 + + {architectural_design} + +Dependability Analysis +---------------------- + +.. toctree:: + :maxdepth: 2 + {dependability_analysis} + +Checklists +---------- + +.. toctree:: + :maxdepth: 2 + + {checklists} From d830eb1387faa098203c43fb635f396352847f00 Mon Sep 17 00:00:00 2001 From: Markus Bechter Date: Wed, 28 Jan 2026 17:56:29 +0100 Subject: [PATCH 9/9] Fixed formating issues --- bazel/rules/score_module/private/score_component.bzl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bazel/rules/score_module/private/score_component.bzl b/bazel/rules/score_module/private/score_component.bzl index f3bddc0..6848d73 100644 --- a/bazel/rules/score_module/private/score_component.bzl +++ b/bazel/rules/score_module/private/score_component.bzl @@ -29,7 +29,6 @@ load("//bazel/rules/score_module/private:sphinx_module.bzl", "sphinx_module") def _get_sphinx_files(target): return target[SphinxSourcesInfo].srcs.to_list() - def _filter_doc_files(files): """Filter files to only include documentation files. @@ -140,7 +139,7 @@ def _create_artifact_symlink(ctx, artifact_name, artifact_file, relative_path): Declared output file """ output_file = ctx.actions.declare_file( - ctx.label.name + "/" + artifact_name + "/" + relative_path + ctx.label.name + "/" + artifact_name + "/" + relative_path, ) ctx.actions.symlink( @@ -184,7 +183,7 @@ def _process_artifact_files(ctx, artifact_name, label): ctx, artifact_name, artifact_file, - relative_path + relative_path, ) output_files.append(output_file) @@ -219,7 +218,7 @@ def _process_artifact_type(ctx, artifact_name): label_outputs, label_refs = _process_artifact_files( ctx, artifact_name, - label + label, ) output_files.extend(label_outputs) index_refs.extend(label_refs)