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 2f5d939..0b792f4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -28,9 +28,7 @@ bazel_dep(name = "rules_java", version = "8.15.1") bazel_dep(name = "rules_rust", version = "0.61.0") bazel_dep(name = "rules_multitool", version = "1.9.0") bazel_dep(name = "score_rust_policies", version = "0.0.2") - -bazel_dep(name = "bazel_skylib", version = "1.7.1", dev_dependency = True) - +bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") ############################################################################### @@ -95,3 +93,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 = "718388bd5e0f10debd97a131d238ad4d758ddd1e", + 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/README.md b/README.md index bded050..571b805 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,15 @@ bazel run //:rust_coverage -- --min-line-coverage 80 ## 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 @@ -78,6 +79,7 @@ The available import targets are: - rust_coverage_report ## Format the tooling repository -```bash + +```bash bazel run //:format.fix ``` diff --git a/bazel/rules/rules_score/BUILD b/bazel/rules/rules_score/BUILD new file mode 100644 index 0000000..03f7457 --- /dev/null +++ b/bazel/rules/rules_score/BUILD @@ -0,0 +1,51 @@ +load( + "//bazel/rules/rules_score:rules_score.bzl", + "sphinx_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", + ], +) + +sphinx_module( + name = "rules_score_doc", + srcs = glob( + [ + "docs/**/*.rst", + "docs/**/*.puml", + ], + allow_empty = True, + ), + index = "docs/index.rst", + visibility = ["//visibility:public"], + deps = [ + "@score_process//:score_process_module", + ], +) diff --git a/bazel/rules/rules_score/docs/index.rst b/bazel/rules/rules_score/docs/index.rst new file mode 100644 index 0000000..c6ea28a --- /dev/null +++ b/bazel/rules/rules_score/docs/index.rst @@ -0,0 +1,258 @@ +SCORE Rules for Bazel +===================== + +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 + :local: + + +Overview +-------- + +The ``rules_score`` package provides Bazel rules for structuring and documenting safety-critical software following S-CORE process guidelines: + +**Documentation Rule:** + +- ``sphinx_module``: Generic rule for building Sphinx HTML documentation with dependency support + +**Artifact Rules:** + +- ``feature_requirements``: High-level feature specifications +- ``component_requirements``: Component-level requirements +- ``assumptions_of_use``: Safety-relevant operating conditions +- ``architectural_design``: Software architecture documentation +- ``safety_analysis``: Detailed safety analysis (FMEA, FTA) +- ``dependability_analysis``: Comprehensive safety analysis results + +**Structural Rules:** + +- ``unit``: Smallest testable software element (design + implementation + tests) +- ``component``: Collection of units providing specific functionality +- ``dependable_element``: Complete Safety Element out of Context (SEooC) with full documentation + +All rules support cross-module dependencies for automatic sphinx-needs integration and HTML merging. + + +sphinx_module +------------- + +Builds Sphinx-based HTML documentation from RST source files with support for dependencies and cross-referencing. + +.. code-block:: python + + sphinx_module( + name = "my_docs", + srcs = glob(["docs/**/*.rst"]), + index = "docs/index.rst", + deps = ["@external_module//:docs"], + ) + +**Key Parameters:** + +- ``srcs``: RST/MD source files +- ``index``: Main index.rst file +- ``deps``: Other sphinx_module or dependable_element targets for cross-referencing +- ``sphinx``: Sphinx build binary (default: ``//bazel/rules/rules_score:score_build``) + +**Output:** ``/html/`` with merged dependency documentation + + +Artifact Rules +-------------- + +Artifact rules define S-CORE process work products. All provide ``SphinxSourcesInfo`` for documentation generation. + +**feature_requirements** + +.. code-block:: python + + feature_requirements( + name = "features", + srcs = ["docs/features.rst"], + ) + +**component_requirements** + +.. code-block:: python + + component_requirements( + name = "requirements", + srcs = ["docs/requirements.rst"], + feature_requirement = [":features"], + ) + +**assumptions_of_use** + +.. code-block:: python + + assumptions_of_use( + name = "aous", + srcs = ["docs/assumptions.rst"], + ) + +**architectural_design** + +.. code-block:: python + + architectural_design( + name = "architecture", + static = ["docs/static_arch.rst"], + dynamic = ["docs/dynamic_arch.rst"], + ) + +**safety_analysis** + +.. code-block:: python + + safety_analysis( + name = "safety", + controlmeasures = ["docs/controls.rst"], + failuremodes = ["docs/failures.rst"], + fta = ["docs/fta.rst"], + arch_design = ":architecture", + ) + +**dependability_analysis** + +.. code-block:: python + + dependability_analysis( + name = "analysis", + arch_design = ":architecture", + dfa = ["docs/dfa.rst"], + safety_analysis = [":safety"], + ) + + +Structural Rules +---------------- + +**unit** + +Define the smallest testable software element. + +.. code-block:: python + + unit( + name = "my_unit", + unit_design = [":architecture"], + implementation = ["//src:lib"], + tests = ["//tests:unit_test"], + ) + +**component** + +Define a collection of units. + +.. code-block:: python + + component( + name = "my_component", + component_requirements = [":requirements"], + units = [":my_unit"], + implementation = ["//src:binary"], + tests = ["//tests:integration_test"], + ) + +**dependable_element** + +Define a complete SEooC with automatic documentation generation. + +.. code-block:: python + + dependable_element( + name = "my_seooc", + description = "My safety-critical component", + assumptions_of_use = [":aous"], + requirements = [":requirements"], + architectural_design = [":architecture"], + dependability_analysis = [":analysis"], + components = [":my_component"], + tests = ["//tests:system_test"], + deps = ["@platform//:platform_module"], + ) + +**Generated Targets:** + +- ````: Sphinx module with HTML documentation +- ``_needs``: Sphinx-needs JSON for cross-referencing +- ``_index``: Generated index.rst with artifact structure + + +Dependency Management +--------------------- + +Use ``deps`` for cross-module references. HTML is automatically merged: + +.. code-block:: text + + /html/ + ├── index.html # Main documentation + ├── _static/ + ├── dependency1/ # Merged dependency + └── dependency2/ + + +Complete Example +---------------- + +.. code-block:: python + + load("//bazel/rules/rules_score:rules_score.bzl", + "architectural_design", "assumptions_of_use", + "component", "component_requirements", + "dependability_analysis", "dependable_element", + "feature_requirements", "safety_analysis", "unit") + + # Artifacts + feature_requirements(name = "features", srcs = ["docs/features.rst"]) + component_requirements(name = "reqs", srcs = ["docs/reqs.rst"], + feature_requirement = [":features"]) + assumptions_of_use(name = "aous", srcs = ["docs/aous.rst"]) + architectural_design(name = "arch", static = ["docs/arch.rst"], + dynamic = ["docs/dynamic.rst"]) + safety_analysis(name = "safety", arch_design = ":arch") + dependability_analysis(name = "analysis", arch_design = ":arch", + dfa = ["docs/dfa.rst"], + safety_analysis = [":safety"]) + + # Implementation + cc_library(name = "kvs_lib", srcs = ["kvs.cpp"], hdrs = ["kvs.h"]) + cc_test(name = "kvs_test", srcs = ["kvs_test.cpp"], deps = [":kvs_lib"]) + + # Structure + unit(name = "kvs_unit", unit_design = [":arch"], + implementation = [":kvs_lib"], tests = [":kvs_test"]) + component(name = "kvs_component", requirements = [":reqs"], + units = [":kvs_unit"], implementation = [":kvs_lib"], tests = []) + + # SEooC + dependable_element( + name = "persistency_kvs", + description = "Key-Value Store for persistent data storage", + assumptions_of_use = [":aous"], + requirements = [":reqs"], + architectural_design = [":arch"], + dependability_analysis = [":analysis"], + components = [":kvs_component"], + tests = [], + deps = ["@score_process//:score_process_module"], + ) + +Build: + +.. code-block:: bash + + bazel build //:persistency_kvs + # Output: bazel-bin/persistency_kvs/html/ + +Reference Implementation +------------------------ + +See complete examples in the test BUILD file: + +.. literalinclude:: ../test/BUILD + :language: python + :caption: test/BUILD diff --git a/bazel/rules/rules_score/private/BUILD b/bazel/rules/rules_score/private/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/bazel/rules/rules_score/private/architectural_design.bzl b/bazel/rules/rules_score/private/architectural_design.bzl new file mode 100644 index 0000000..ddd8fdd --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score: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/rules_score/private/assumptions_of_use.bzl b/bazel/rules/rules_score/private/assumptions_of_use.bzl new file mode 100644 index 0000000..158b92b --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score:providers.bzl", "SphinxSourcesInfo") +load("//bazel/rules/rules_score/private:feature_requirements.bzl", "FeatureRequirementsInfo") + +# ============================================================================ +# 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/rules_score/private/component.bzl b/bazel/rules/rules_score/private/component.bzl new file mode 100644 index 0000000..8a8c29a --- /dev/null +++ b/bazel/rules/rules_score/private/component.bzl @@ -0,0 +1,169 @@ +# ******************************************************************************* +# 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 build rules for S-CORE projects. + +This module provides macros and rules for defining software components +following S-CORE process guidelines. A component consists of multiple units +with associated requirements and tests. +""" + +load("//bazel/rules/rules_score:providers.bzl", "ComponentInfo", "SphinxSourcesInfo") + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _component_impl(ctx): + """Implementation for component rule. + + Collects component requirements, units, and tests and provides them + through the ComponentInfo provider. + + Args: + ctx: Rule context + + Returns: + List of providers including DefaultInfo and ComponentInfo + """ + + # Collect requirements files from component_requirements targets + requirements_files = [] + for req_target in ctx.attr.requirements: + if SphinxSourcesInfo in req_target: + requirements_files.append(req_target[SphinxSourcesInfo].srcs) + + requirements_depset = depset(transitive = requirements_files) + + # Collect implementation targets + implementation_depset = depset(ctx.attr.implementation) + + # Collect units and tests + units_depset = depset(ctx.attr.units) + tests_depset = depset(ctx.attr.tests) + + # Combine all files for DefaultInfo + all_files = depset( + transitive = [requirements_depset], + ) + + return [ + DefaultInfo(files = all_files), + ComponentInfo( + name = ctx.label.name, + requirements = requirements_depset, + implementation = implementation_depset, + units = units_depset, + tests = tests_depset, + ), + SphinxSourcesInfo( + srcs = all_files, + transitive_srcs = all_files, + ), + ] + +# ============================================================================ +# Rule Definition +# ============================================================================ + +_component = rule( + implementation = _component_impl, + doc = "Defines a software component composed of multiple units for S-CORE process compliance", + attrs = { + "requirements": attr.label_list( + mandatory = True, + doc = "Component requirements artifacts (typically component_requirements targets)", + ), + "implementation": attr.label_list( + doc = "Implementation targets (libraries, binaries) that realize this component", + default = [], + ), + "units": attr.label_list( + mandatory = True, + doc = "Unit targets that comprise this component", + ), + "tests": attr.label_list( + mandatory = True, + doc = "Component-level integration test targets", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def component( + name, + units = None, + tests = [], + implementation = [], + requirements = None, + components = None, + testonly = True, + visibility = None): + """Define a software component following S-CORE process guidelines. + + A component is a collection of related units that together provide + a specific functionality. It consists of: + - Component requirements: Requirements specification for the component + - Implementation: Concrete libraries/binaries that realize the component + - Units: Individual software units that implement the requirements + - Tests: Integration tests that verify the component as a whole + + Args: + name: The name of the component. Used as the target name. + component_requirements: List of labels to component_requirements targets + that define the requirements for this component. + requirements: Alias for component_requirements (use one or the other). + implementation: List of labels to implementation targets (cc_library, + cc_binary, etc.) that realize this component. + units: List of labels to unit targets that comprise this component. + components: Alias for units (use one or the other). + tests: List of labels to Bazel test targets that verify the component + integration. + testonly: If true, only testonly targets can depend on this component. + visibility: Bazel visibility specification for the component target. + + Example: + ```python + component( + name = "kvs_component", + requirements = [":kvs_component_requirements"], + implementation = [":kvs_lib", ":kvs_tool"], + units = [":kvs_unit1", ":kvs_unit2"], + tests = ["//persistency/kvs/tests:score_kvs_component_integration_tests"], + visibility = ["//visibility:public"], + ) + ``` + """ + + # Support both old parameter names and new aliases + final_requirements = requirements + final_units = units if units != None else components + + if final_requirements == None: + fail("component() requires 'requirements' parameter") + if final_units == None: + fail("component() requires either 'units' or 'components' parameter") + + _component( + name = name, + requirements = final_requirements, + implementation = implementation, + units = final_units, + tests = tests, + testonly = testonly, + visibility = visibility, + ) diff --git a/bazel/rules/rules_score/private/component_requirements.bzl b/bazel/rules/rules_score/private/component_requirements.bzl new file mode 100644 index 0000000..eda94d6 --- /dev/null +++ b/bazel/rules/rules_score/private/component_requirements.bzl @@ -0,0 +1,139 @@ +# ******************************************************************************* +# 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/rules_score:providers.bzl", "SphinxSourcesInfo") +load("//bazel/rules/rules_score/private:feature_requirements.bzl", "FeatureRequirementsInfo") + +# ============================================================================ +# Provider Definition +# ============================================================================ + +ComponentRequirementsInfo = provider( + doc = "Provider for component requirements artifacts", + fields = { + "srcs": "Depset of source files containing component requirements", + "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, + 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", + ), + }, +) + +# ============================================================================ +# 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/rules_score/private/dependability_analysis.bzl b/bazel/rules/rules_score/private/dependability_analysis.bzl new file mode 100644 index 0000000..05c7f36 --- /dev/null +++ b/bazel/rules/rules_score/private/dependability_analysis.bzl @@ -0,0 +1,191 @@ +# ******************************************************************************* +# 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/rules_score:providers.bzl", "SphinxSourcesInfo") +load("//bazel/rules/rules_score/private:architectural_design.bzl", "ArchitecturalDesignInfo") +load("//bazel/rules/rules_score/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", + "fmea": "Depset of Failure Mode and Effects 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) + fmea_files = depset(ctx.files.fmea) + + # 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, fmea_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, + fmea = fmea_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", + ), + "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, + doc = "Reference to architectural_design target for traceability", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def dependability_analysis( + name, + safety_analysis = [], + dfa = [], + fmea = [], + 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. + 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. + 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"], + fmea = ["failure_mode_effects_analysis.rst"], + arch_design = ":my_architectural_design", + ) + ``` + """ + _dependability_analysis( + name = name, + safety_analysis = safety_analysis, + dfa = dfa, + fmea = fmea, + arch_design = arch_design, + visibility = visibility, + ) diff --git a/bazel/rules/rules_score/private/dependable_element.bzl b/bazel/rules/rules_score/private/dependable_element.bzl new file mode 100644 index 0000000..a5dd097 --- /dev/null +++ b/bazel/rules/rules_score/private/dependable_element.bzl @@ -0,0 +1,569 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +""" +Dependable Element build rules for S-CORE projects. + +This module provides macros and rules for defining dependable elements (Safety +Elements out of Context - SEooC) following S-CORE process guidelines. A dependable +element is a safety-critical component with comprehensive documentation including +assumptions of use, requirements, design, and safety analysis. +""" + +load( + "//bazel/rules/rules_score:providers.bzl", + "DependableElementInfo", + "SphinxSourcesInfo", +) +load("//bazel/rules/rules_score/private:sphinx_module.bzl", "sphinx_module") + +# ============================================================================ +# Helper Functions for Documentation Generation +# ============================================================================ + +def _get_sphinx_files(target): + return target[SphinxSourcesInfo].srcs.to_list() + +def _filter_doc_files(files): + """Filter files to only include documentation files. + + Args: + files: List of files to filter + + Returns: + List of documentation files + """ + return [f for f in files if f.extension in ["rst", "md", "puml", "plantuml", "png", "svg"]] + +def _find_common_directory(files): + """Find the longest common directory path for a list of files. + + Args: + files: List of File objects + + Returns: + 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 + +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"] + +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) + """ + 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 _process_deps(ctx): + """Process deps to generate references to submodule documentation. + + The HTML merger in sphinx_module will copy the HTML directories from deps. + We generate RST bullet list with links to those HTML directories. + + Args: + ctx: Rule context + + Returns: + String containing RST-formatted bullet list of links + """ + if not ctx.attr.deps: + return "" + + # Generate RST bullet list with links to submodule HTML + links = [] + for dep in ctx.attr.deps: + dep_name = dep.label.name + + # Create a link to the index.html that will be merged + # Format: * `Module Name `_ + # Use underscores in name for readability, convert to spaces for display + display_name = dep_name.replace("_", " ").title() + links.append("* `{} <{}/index.html>`_".format(display_name, dep_name)) + + return "\n".join(links) + +# ============================================================================ +# Index Generation Rule Implementation +# ============================================================================ + +def _dependable_element_index_impl(ctx): + """Generate index.rst file with references to all dependable element artifacts. + + This rule creates a Sphinx index.rst file that includes references to all + the documentation artifacts for the dependable element. + + Args: + ctx: Rule context + + Returns: + DefaultInfo provider with generated index.rst file + """ + + # Declare output index file + index_rst = ctx.actions.declare_file(ctx.label.name + "/index.rst") + output_files = [index_rst] + + # Define artifact types to process + # Note: "requirements" can contain both component_requirements and feature_requirements + artifact_types = [ + "assumptions_of_use", + "requirements", + "architectural_design", + "dependability_analysis", + "checklists", + ] + + # Process each artifact type + 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 + + # Process dependencies (submodules) + deps_links = _process_deps(ctx) + + # Generate index file from template + 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["requirements"]), + "{architectural_design}": "\n ".join(artifacts_by_type["architectural_design"]), + "{dependability_analysis}": "\n ".join(artifacts_by_type["dependability_analysis"]), + "{checklists}": "\n ".join(artifacts_by_type["checklists"]), + "{submodules}": deps_links, + }, + ) + + return [ + DefaultInfo(files = depset(output_files)), + ] + +_dependable_element_index = rule( + implementation = _dependable_element_index_impl, + doc = "Generates index.rst file with references to dependable element artifacts", + attrs = { + "module_name": attr.string( + mandatory = True, + doc = "Name of the dependable element module (used as document title)", + ), + "description": attr.string( + mandatory = True, + doc = "Description of the dependable element. Supports RST formatting.", + ), + "assumptions_of_use": attr.label_list( + mandatory = True, + doc = "Assumptions of Use targets or files.", + ), + "requirements": attr.label_list( + mandatory = True, + doc = "Requirements targets (component_requirements, feature_requirements, etc.).", + ), + "architectural_design": attr.label_list( + mandatory = True, + doc = "Architectural design targets or files.", + ), + "dependability_analysis": attr.label_list( + mandatory = True, + doc = "Dependability analysis targets or files.", + ), + "checklists": attr.label_list( + default = [], + doc = "Safety checklists targets or files.", + ), + "template": attr.label( + allow_single_file = [".rst"], + mandatory = True, + doc = "Template file for generating index.rst", + ), + "deps": attr.label_list( + default = [], + doc = "Dependencies on other dependable element modules (submodules).", + ), + }, +) + +# ============================================================================ +# Provider Rule Implementation +# ============================================================================ + +def _dependable_element_provider_impl(ctx): + """Provide DependableElementInfo for a dependable element. + + This rule collects metadata about the dependable element and provides + it through the DependableElementInfo provider. + + Args: + ctx: Rule context + + Returns: + List of providers including DependableElementInfo and SphinxSourcesInfo + """ + + # Collect depsets for each artifact type + assumptions_depset = depset(ctx.files.assumptions_of_use) + requirements_depset = depset(ctx.files.requirements) + arch_design_depset = depset(ctx.files.architectural_design) + dep_analysis_depset = depset(ctx.files.dependability_analysis) + components_depset = depset(ctx.attr.components) + tests_depset = depset(ctx.attr.tests) + + # Collect all source files for Sphinx + all_files = depset( + direct = ctx.files.assumptions_of_use + + ctx.files.requirements + + ctx.files.architectural_design + + ctx.files.dependability_analysis, + ) + + return [ + DependableElementInfo( + name = ctx.label.name, + description = ctx.attr.description, + assumptions_of_use = assumptions_depset, + requirements = requirements_depset, + architectural_design = arch_design_depset, + dependability_analysis = dep_analysis_depset, + consists_of = components_depset, + tests = tests_depset, + ), + SphinxSourcesInfo( + srcs = all_files, + transitive_srcs = all_files, + ), + ] + +_dependable_element_provider = rule( + implementation = _dependable_element_provider_impl, + doc = "Provider rule for dependable element metadata", + attrs = { + "description": attr.string( + mandatory = True, + doc = "Description of the dependable element", + ), + "assumptions_of_use": attr.label_list( + mandatory = True, + doc = "Assumptions of Use targets or files", + ), + "requirements": attr.label_list( + mandatory = True, + doc = "Requirements targets", + ), + "architectural_design": attr.label_list( + mandatory = True, + doc = "Architectural design targets or files", + ), + "dependability_analysis": attr.label_list( + mandatory = True, + doc = "Dependability analysis targets or files", + ), + "components": attr.label_list( + default = [], + doc = "Component and/or unit targets that comprise this element", + ), + "tests": attr.label_list( + default = [], + doc = "Test targets", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def dependable_element( + name, + description, + assumptions_of_use, + requirements, + architectural_design, + dependability_analysis, + components, + tests, + checklists = [], + deps = [], + sphinx = Label("@score_tooling//bazel/rules/rules_score:score_build"), + testonly = True, + visibility = None): + """Define a dependable element (Safety Element out of Context - SEooC) following S-CORE process guidelines. + + This macro creates a complete dependable element with integrated documentation + generation. It generates an index.rst file referencing all artifacts and builds + HTML documentation using the sphinx_module infrastructure. + + A dependable element is a safety-critical component that can be developed + independently and integrated into different systems. It includes comprehensive + documentation covering all aspects required for safety certification. + + Args: + name: The name of the dependable element. Used as the base name for + all generated targets. + description: String containing a high-level description of the element. + This text provides context about what the element does and its purpose. + Supports RST formatting. + assumptions_of_use: List of labels to assumptions_of_use targets that + define the safety-relevant operating conditions and constraints. + requirements: List of labels to requirements targets (component_requirements, + feature_requirements, etc.) that define functional and safety requirements. + architectural_design: List of labels to architectural_design targets that + describe the software architecture and design decisions. + dependability_analysis: List of labels to dependability_analysis targets + containing safety analysis results (FMEA, FMEDA, FTA, DFA, etc.). + components: List of labels to component and/or unit targets that implement + this dependable element. + tests: List of labels to Bazel test targets that verify the dependable + element at the system level (integration tests, system tests). + checklists: Optional list of labels to .rst or .md files containing + safety checklists and verification documents. + deps: Optional list of other module targets this element depends on. + Cross-references will work automatically. + sphinx: Label to sphinx build binary. Default: //bazel/rules/rules_score:score_build + testonly: If True, only testonly targets can depend on this target. + visibility: Bazel visibility specification for the dependable element target. + + Generated Targets: + _provider: Internal metadata provider with DependableElementInfo + _index: Internal rule that generates index.rst and copies artifacts + : Main dependable element target (sphinx_module) with HTML documentation + _needs: Internal target for sphinx-needs JSON generation + + Example: + ```python + dependable_element( + name = "persistency_kvs", + description = ''' + The Key-Value Store (KVS) component provides persistent storage capabilities + for safety-critical applications. + ''', + assumptions_of_use = [":kvs_assumptions_of_use"], + requirements = [":kvs_component_requirements"], + architectural_design = [":kvs_architectural_design"], + dependability_analysis = [":kvs_dependability_analysis"], + components = [":kvs_component", ":kvs_unit"], + tests = ["//persistency/kvs/tests:score_kvs_integration_tests"], + deps = [ + "@score_process//:score_process_module", + "@score_platform//:score_platform_module", + ], + visibility = ["//visibility:public"], + ) + ``` + """ + + # Step 1: Create provider target with DependableElementInfo + _dependable_element_provider( + name = name + "_provider", + description = description, + assumptions_of_use = assumptions_of_use, + requirements = requirements, + architectural_design = architectural_design, + dependability_analysis = dependability_analysis, + components = components, + tests = tests, + testonly = testonly, + visibility = ["//visibility:private"], + ) + + # Step 2: Generate index.rst and collect all artifacts + _dependable_element_index( + name = name + "_index", + module_name = name, + description = description, + template = Label("//bazel/rules/rules_score:templates/seooc_index.template.rst"), + assumptions_of_use = assumptions_of_use, + requirements = requirements, + architectural_design = architectural_design, + dependability_analysis = dependability_analysis, + checklists = checklists, + deps = deps, + testonly = testonly, + visibility = ["//visibility:private"], + ) + + # Step 3: Create sphinx_module using generated index and artifacts + sphinx_module( + name = name, + srcs = [":" + name + "_index"], + index = ":" + name + "_index", + deps = deps, + sphinx = sphinx, + testonly = testonly, + visibility = visibility, + ) diff --git a/bazel/rules/rules_score/private/feature_requirements.bzl b/bazel/rules/rules_score/private/feature_requirements.bzl new file mode 100644 index 0000000..fd8dec1 --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score: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/rules_score/private/safety_analysis.bzl b/bazel/rules/rules_score/private/safety_analysis.bzl new file mode 100644 index 0000000..89d4ab0 --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score:providers.bzl", "SphinxSourcesInfo") +load("//bazel/rules/rules_score/private:architectural_design.bzl", "ArchitecturalDesignInfo") + +# ============================================================================ +# 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/rules_score/private/sphinx_module.bzl b/bazel/rules/rules_score/private/sphinx_module.bzl new file mode 100644 index 0000000..2ab3d29 --- /dev/null +++ b/bazel/rules/rules_score/private/sphinx_module.bzl @@ -0,0 +1,302 @@ +# ====================================================================================== +# Providers +# ====================================================================================== + +SphinxModuleInfo = provider( + doc = "Provider for Sphinx HTML module documentation", + fields = { + "html_dir": "Directory containing HTML files", + }, +) + +SphinxNeedsInfo = 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 sphinx_module targets this module depends on for intersphinx.", + ), + "_config_template": attr.label( + default = Label("//bazel/rules/rules_score:templates/conf.template.py"), + allow_single_file = True, + doc = "Template for generating default conf.py", + ), + "_html_merge_tool": attr.label( + default = Label("//bazel/rules/rules_score: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[SphinxNeedsInfo].needs_json_files for dep in ctx.attr.deps if SphinxNeedsInfo in dep] + needs_json_files = depset([needs_output], transitive = transitive_needs) + + return [ + DefaultInfo( + files = needs_json_files, + ), + SphinxNeedsInfo( + 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 SphinxNeedsInfo 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[SphinxNeedsInfo].needs_json_file.path, # Use direct file + "id_prefix": "", + "css_class": "", + } + + for dep in ctx.attr.deps: + if SphinxModuleInfo in dep: + modules.extend([dep[SphinxModuleInfo].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: %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 SphinxModuleInfo in dep: + dep_html_dir = dep[SphinxModuleInfo].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])), + SphinxModuleInfo( + 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 sphinx_module( + name, + srcs, + index, + config = None, + deps = [], + sphinx = Label("//bazel/rules/rules_score:score_build"), + testonly = False, + 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 sphinx_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, + testonly = testonly, + visibility = visibility, + ) + + _score_html( + name = name, + srcs = srcs, + config = config, + index = index, + deps = deps, + needs = [d + "_needs" for d in deps], + sphinx = sphinx, + testonly = testonly, + visibility = visibility, + ) diff --git a/bazel/rules/rules_score/private/unit.bzl b/bazel/rules/rules_score/private/unit.bzl new file mode 100644 index 0000000..835b3a1 --- /dev/null +++ b/bazel/rules/rules_score/private/unit.bzl @@ -0,0 +1,157 @@ +# ******************************************************************************* +# 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 build rules for S-CORE projects. + +This module provides macros and rules for defining software units +following S-CORE process guidelines. A unit is the smallest testable +software element with associated design, implementation, and tests. +""" + +load("//bazel/rules/rules_score:providers.bzl", "SphinxSourcesInfo", "UnitInfo") + +# ============================================================================ +# Private Rule Implementation +# ============================================================================ + +def _unit_impl(ctx): + """Implementation for unit rule. + + Collects unit design artifacts, implementation targets, and tests + and provides them through the UnitInfo provider. + + Args: + ctx: Rule context + + Returns: + List of providers including DefaultInfo and UnitInfo + """ + + # Collect design files from unit_design targets + design_files = [] + for design_target in ctx.attr.unit_design: + if SphinxSourcesInfo in design_target: + design_files.append(design_target[SphinxSourcesInfo].srcs) + + design_depset = depset(transitive = design_files) + + # Collect implementation and test targets + # Include scope targets in the implementation depset + implementation_depset = depset(ctx.attr.implementation + ctx.attr.scope) + tests_depset = depset(ctx.attr.tests) + + # Combine all files for DefaultInfo + all_files = depset( + transitive = [design_depset], + ) + + return [ + DefaultInfo(files = all_files), + UnitInfo( + name = ctx.label.name, + unit_design = design_depset, + implementation = implementation_depset, + tests = tests_depset, + ), + SphinxSourcesInfo( + srcs = all_files, + transitive_srcs = all_files, + ), + ] + +# ============================================================================ +# Rule Definition +# ============================================================================ + +_unit = rule( + implementation = _unit_impl, + doc = "Defines a software unit with design, implementation, and tests for S-CORE process compliance", + attrs = { + "unit_design": attr.label_list( + mandatory = True, + doc = "Unit design artifacts (typically architectural_design targets)", + ), + "implementation": attr.label_list( + mandatory = True, + doc = "Implementation targets (cc_library, py_library, rust_library, etc.)", + ), + "scope": attr.label_list( + default = [], + doc = "Additional not explicitly named targets which are needed for the unit implementation", + ), + "tests": attr.label_list( + mandatory = True, + doc = "Test targets that verify the unit (cc_test, py_test, rust_test, etc.)", + ), + }, +) + +# ============================================================================ +# Public Macro +# ============================================================================ + +def unit( + name, + unit_design, + implementation, + tests, + scope = [], + testonly = True, + visibility = None): + """Define a software unit following S-CORE process guidelines. + + A unit is the smallest testable software element in the S-CORE process. + It consists of: + - Unit design: Design documentation and diagrams + - Implementation: Source code that realizes the design + - Tests: Test cases that verify the implementation + + Args: + name: The name of the unit. Used as the target name. + unit_design: List of labels to architectural_design targets or design + documentation that describes the unit's internal structure and behavior. + implementation: List of labels to Bazel targets representing the actual + implementation (cc_library, py_library, rust_library, etc.). + scope: Optional list of additional targets needed for the unit implementation + but not explicitly named in the implementation list. Default is empty list. + tests: List of labels to Bazel test targets (cc_test, py_test, rust_test, etc.) + that verify the unit implementation. + testonly: If true, only testonly targets can depend on this unit. Set to true + when the unit depends on testonly targets like tests. + visibility: Bazel visibility specification for the unit target. + + Example: + ```python + unit( + name = "kvs_unit1", + unit_design = [":kvs_architectural_design"], + implementation = [ + "//persistency/kvs:lib1", + "//persistency/kvs:lib2", + "//persistency/kvs:lib3", + ], + tests = ["//persistency/kvs/tests:score_kvs_component_tests"], + visibility = ["//visibility:public"], + ) + ``` + """ + _unit( + name = name, + unit_design = unit_design, + implementation = implementation, + scope = scope, + tests = tests, + testonly = testonly, + visibility = visibility, + ) diff --git a/bazel/rules/rules_score/providers.bzl b/bazel/rules/rules_score/providers.bzl new file mode 100644 index 0000000..d4d8746 --- /dev/null +++ b/bazel/rules/rules_score/providers.bzl @@ -0,0 +1,72 @@ +# ******************************************************************************* +# 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 dependable_element. + """, + 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", + }, +) + +UnitInfo = provider( + doc = "Provider for unit artifacts", + fields = { + "name": "Name of the unit target", + "unit_design": "Depset of unit design artifacts (architectural design)", + "implementation": "Depset of implementation targets (libraries, binaries)", + "tests": "Depset of test targets", + }, +) + +ComponentInfo = provider( + doc = "Provider for component artifacts", + fields = { + "name": "Name of the component target", + "requirements": "Depset of component requirements artifacts", + "implementation": "Depset of implementation targets (libraries, binaries)", + "units": "Depset of unit targets that comprise this component", + "tests": "Depset of component-level integration test targets", + }, +) + +DependableElementInfo = provider( + doc = "Provider for dependable element (SEooC) artifacts", + fields = { + "name": "Name of the dependable element target", + "description": "Description of the dependable element", + "assumptions_of_use": "Depset of assumptions of use artifacts", + "requirements": "Depset of requirements artifacts (safety and feature requirements)", + "architectural_design": "Depset of architectural design artifacts", + "dependability_analysis": "Depset of dependability analysis artifacts", + "consists_of": "Depset of component/unit targets that comprise this element", + "tests": "Depset of system-level integration test targets", + }, +) diff --git a/bazel/rules/rules_score/rules_score.bzl b/bazel/rules/rules_score/rules_score.bzl new file mode 100644 index 0000000..907b4fa --- /dev/null +++ b/bazel/rules/rules_score/rules_score.bzl @@ -0,0 +1,64 @@ +load("@rules_python//sphinxdocs:sphinx.bzl", "sphinx_docs") +load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library") +load( + "//bazel/rules/rules_score:providers.bzl", + _ComponentInfo = "ComponentInfo", + _DependableElementInfo = "DependableElementInfo", + _SphinxSourcesInfo = "SphinxSourcesInfo", + _UnitInfo = "UnitInfo", +) +load( + "//bazel/rules/rules_score/private:architectural_design.bzl", + _architectural_design = "architectural_design", +) +load( + "//bazel/rules/rules_score/private:assumptions_of_use.bzl", + _assumptions_of_use = "assumptions_of_use", +) +load( + "//bazel/rules/rules_score/private:component.bzl", + _component = "component", +) +load( + "//bazel/rules/rules_score/private:component_requirements.bzl", + _component_requirements = "component_requirements", +) +load( + "//bazel/rules/rules_score/private:dependability_analysis.bzl", + _dependability_analysis = "dependability_analysis", +) +load( + "//bazel/rules/rules_score/private:dependable_element.bzl", + _dependable_element = "dependable_element", +) +load( + "//bazel/rules/rules_score/private:feature_requirements.bzl", + _feature_requirements = "feature_requirements", +) +load( + "//bazel/rules/rules_score/private:safety_analysis.bzl", + _safety_analysis = "safety_analysis", +) +load( + "//bazel/rules/rules_score/private:sphinx_module.bzl", + _sphinx_module = "sphinx_module", +) +load( + "//bazel/rules/rules_score/private:unit.bzl", + _unit = "unit", +) + +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 +unit = _unit +component = _component +dependable_element = _dependable_element +SphinxSourcesInfo = _SphinxSourcesInfo +UnitInfo = _UnitInfo +ComponentInfo = _ComponentInfo +DependableElementInfo = _DependableElementInfo diff --git a/bazel/rules/rules_score/src/sphinx_html_merge.py b/bazel/rules/rules_score/src/sphinx_html_merge.py new file mode 100644 index 0000000..60dfaa4 --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/src/sphinx_wrapper.py b/bazel/rules/rules_score/src/sphinx_wrapper.py new file mode 100644 index 0000000..1376057 --- /dev/null +++ b/bazel/rules/rules_score/src/sphinx_wrapper.py @@ -0,0 +1,262 @@ +""" +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 +import re +import sys +from contextlib import redirect_stdout, redirect_stderr + +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__) + +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]: + """ + 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) + # 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}") + 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/rules_score/templates/conf.template.py b/bazel/rules/rules_score/templates/conf.template.py new file mode 100644 index 0000000..e916952 --- /dev/null +++ b/bazel/rules/rules_score/templates/conf.template.py @@ -0,0 +1,207 @@ +# ******************************************************************************* +# 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 +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}" +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(): + logger.info(f"{NEEDS_EXTERNAL_FILE} not found - no external dependencies") + return [] + + 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: + logger.error(f"Failed to parse {NEEDS_EXTERNAL_FILE}: {e}") + return [] + except Exception as e: + logger.error(f"Failed to read {NEEDS_EXTERNAL_FILE}: {e}") + return [] + + workspace_root = find_workspace_root() + logger.info(f"Workspace root: {workspace_root}") + + external_needs = [] + for key, config in needs_dict.items(): + if "json_path" not in config: + logger.warning( + f"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) + + 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) + + return external_needs + + +def verify_config(app: Any, config: Any) -> None: + """ + Initialize and verify external needs configuration. + + This is called during Sphinx's config-inited event to ensure + 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 + + 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]: + """ + 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 +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 +# config object in the verify_config callback during the config-inited event +needs_external_needs = load_external_needs() diff --git a/bazel/rules/rules_score/templates/seooc_index.template.rst b/bazel/rules/rules_score/templates/seooc_index.template.rst new file mode 100644 index 0000000..c545f32 --- /dev/null +++ b/bazel/rules/rules_score/templates/seooc_index.template.rst @@ -0,0 +1,63 @@ +.. ******************************************************************************* +.. 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} + +Assumptions of Use +------------------ + +.. toctree:: + :maxdepth: 2 + + {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} + +Submodules +---------- +This module includes the following submodules: + +{submodules} diff --git a/bazel/rules/rules_score/test/BUILD b/bazel/rules/rules_score/test/BUILD new file mode 100644 index 0000000..9317b62 --- /dev/null +++ b/bazel/rules/rules_score/test/BUILD @@ -0,0 +1,331 @@ +# ******************************************************************************* +# 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( + "//bazel/rules/rules_score:rules_score.bzl", + "architectural_design", + "assumptions_of_use", + "component", + "component_requirements", + "dependability_analysis", + "dependable_element", + "feature_requirements", + "safety_analysis", + "sphinx_module", + "unit", +) +load( + ":html_generation_test.bzl", + "html_merging_test", + "module_dependencies_test", + "needs_transitive_test", + "sphinx_module_test_suite", +) +load( + ":seooc_test.bzl", + "seooc_artifacts_copied_test", + "seooc_needs_provider_test", + "seooc_sphinx_module_generated_test", +) +load( + ":unit_component_test.bzl", + "component_provider_test", + "component_sphinx_sources_test", + "dependable_element_provider_test", + "dependable_element_sphinx_sources_test", + "unit_component_test_suite", + "unit_provider_test", + "unit_sphinx_sources_test", +) + +package(default_visibility = ["//visibility:public"]) + +# ============================================================================ +# 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) +sphinx_module( + name = "module_c_lib", + srcs = glob(["fixtures/module_c/*.rst"]), + index = "fixtures/module_c/index.rst", + sphinx = "//bazel/rules/rules_score:score_build", +) + +sphinx_module( + name = "module_b_lib", + srcs = glob(["fixtures/module_b/*.rst"]), + index = "fixtures/module_b/index.rst", + sphinx = "//bazel/rules/rules_score:score_build", + deps = [":module_c_lib"], +) + +sphinx_module( + name = "module_a_lib", + srcs = glob(["fixtures/module_a/*.rst"]), + index = "fixtures/module_a/index.rst", + sphinx = "//bazel/rules/rules_score:score_build", + deps = [ + ":module_b_lib", + ":module_c_lib", + ], +) + +# 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", +) + +dependable_element( + name = "seooc_test_lib", + architectural_design = [":arch_design"], + assumptions_of_use = [":aous"], + components = [], + dependability_analysis = [":dependability_analysis_target"], + description = "Test SEooC module demonstrating S-CORE process compliance structure.", + requirements = [":comp_req"], + tests = [], + deps = [ + ":module_c_lib", # dependency to other seoocs/score_components + ], +) + +# ============================================================================ +# Test Fixtures - Unit, Component, and Dependable Element +# ============================================================================ + +# Mock implementation targets with dummy functions +cc_library( + name = "mock_lib1", + srcs = ["fixtures/mock_lib1.cc"], +) + +cc_library( + name = "mock_lib2", + srcs = ["fixtures/mock_lib2.cc"], +) + +cc_binary( + name = "test_component_binary", + srcs = ["fixtures/test_component_main.cc"], + deps = [ + ":mock_lib1", + ":mock_lib2", + ], +) + +cc_test( + name = "test_unit_tests", + testonly = True, + srcs = ["fixtures/test_unit_test.cc"], + tags = ["manual"], + deps = [ + ":mock_lib1", + ":mock_lib2", + ], +) + +# Test Unit +unit( + name = "test_unit", + testonly = True, + tests = [":test_unit_tests"], + unit_design = [":arch_design"], + implementation = [ + ":mock_lib1", + ":mock_lib2", + ], +) + +# Test Component +component( + name = "test_component", + testonly = True, + requirements = [":comp_req"], + tests = [], # Empty for testing + units = [":test_unit"], + implementation = [":test_component_binary"], +) + +# Test Dependable Element +dependable_element( + name = "test_dependable_element", + testonly = True, + architectural_design = [":arch_design"], + assumptions_of_use = [":aous"], + components = [":test_component"], + dependability_analysis = [":dependability_analysis_target"], + description = "Test dependable element for unit testing", + requirements = [":comp_req"], + tests = [], # Empty for testing +) + +# ============================================================================ +# Test Instantiations - HTML Generation Tests +# ============================================================================ + +# Needs Generation Tests +needs_transitive_test( + name = "needs_transitive_test", + target_under_test = ":module_b_lib_needs", +) + +# Dependency Tests +module_dependencies_test( + name = "module_dependencies_test", + target_under_test = ":module_a_lib", +) + +html_merging_test( + name = "html_merging_test", + target_under_test = ":module_a_lib", +) + +# ============================================================================ +# SEooC-Specific Tests +# ============================================================================ + +# Test that all artifacts are copied +seooc_artifacts_copied_test( + name = "seooc_tests_artifacts_copied", + target_under_test = ":seooc_test_lib_index", +) + +# 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", +) + +# 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 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_needs_provider", + ":seooc_tests_sphinx_module_generated", + ], +) + +# ============================================================================ +# Unit, Component, and Dependable Element Test Instantiations +# ============================================================================ + +# Unit tests +unit_provider_test( + name = "unit_provider_test", + target_under_test = ":test_unit", +) + +unit_sphinx_sources_test( + name = "unit_sphinx_sources_test", + target_under_test = ":test_unit", +) + +# Component tests +component_provider_test( + name = "component_provider_test", + target_under_test = ":test_component", +) + +component_sphinx_sources_test( + name = "component_sphinx_sources_test", + target_under_test = ":test_component", +) + +# Dependable Element tests +dependable_element_provider_test( + name = "dependable_element_provider_test", + target_under_test = ":test_dependable_element_provider", +) + +dependable_element_sphinx_sources_test( + name = "dependable_element_sphinx_sources_test", + target_under_test = ":test_dependable_element_provider", +) + +# Unit, Component, and Dependable Element test suite +unit_component_test_suite(name = "unit_component_tests") + +# ============================================================================ +# Combined Test Suite +# ============================================================================ + +# Combined test suite for all tests +test_suite( + name = "all_tests", + tests = [ + ":seooc_tests", + ":sphinx_module_tests", + ":unit_component_tests", + ], +) diff --git a/bazel/rules/rules_score/test/fixtures/mock_lib1.cc b/bazel/rules/rules_score/test/fixtures/mock_lib1.cc new file mode 100644 index 0000000..599e3c1 --- /dev/null +++ b/bazel/rules/rules_score/test/fixtures/mock_lib1.cc @@ -0,0 +1,4 @@ +// Mock implementation for testing purposes +int mock_function_1() { + return 42; +} diff --git a/bazel/rules/rules_score/test/fixtures/mock_lib2.cc b/bazel/rules/rules_score/test/fixtures/mock_lib2.cc new file mode 100644 index 0000000..588f15d --- /dev/null +++ b/bazel/rules/rules_score/test/fixtures/mock_lib2.cc @@ -0,0 +1,4 @@ +// Mock implementation for testing purposes +int mock_function_2() { + return 84; +} diff --git a/bazel/rules/rules_score/test/fixtures/mock_test.sh b/bazel/rules/rules_score/test/fixtures/mock_test.sh new file mode 100755 index 0000000..d5aa21e --- /dev/null +++ b/bazel/rules/rules_score/test/fixtures/mock_test.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# ******************************************************************************* +# 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 +# ******************************************************************************* + +# Mock test script that always succeeds +# Used for testing rule structure without actual implementation + +exit 0 diff --git a/bazel/rules/rules_score/test/fixtures/module_a/index.rst b/bazel/rules/rules_score/test/fixtures/module_a/index.rst new file mode 100644 index 0000000..573ad4b --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/module_b/index.rst b/bazel/rules/rules_score/test/fixtures/module_b/index.rst new file mode 100644 index 0000000..3155c10 --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/module_c/index.rst b/bazel/rules/rules_score/test/fixtures/module_c/index.rst new file mode 100644 index 0000000..b73ae61 --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/seooc_test/architectural_design.rst b/bazel/rules/rules_score/test/fixtures/seooc_test/architectural_design.rst new file mode 100644 index 0000000..02e96f7 --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/seooc_test/assumptions_of_use.rst b/bazel/rules/rules_score/test/fixtures/seooc_test/assumptions_of_use.rst new file mode 100644 index 0000000..fae172c --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/seooc_test/component_requirements.rst b/bazel/rules/rules_score/test/fixtures/seooc_test/component_requirements.rst new file mode 100644 index 0000000..1d7f90c --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/seooc_test/dependability_analysis.rst b/bazel/rules/rules_score/test/fixtures/seooc_test/dependability_analysis.rst new file mode 100644 index 0000000..ea5b518 --- /dev/null +++ b/bazel/rules/rules_score/test/fixtures/seooc_test/dependability_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/rules_score/test/fixtures/seooc_test/dfa.rst b/bazel/rules/rules_score/test/fixtures/seooc_test/dfa.rst new file mode 100644 index 0000000..7b2e30d --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/seooc_test/dynamic_architecture.rst b/bazel/rules/rules_score/test/fixtures/seooc_test/dynamic_architecture.rst new file mode 100644 index 0000000..33cf03f --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/seooc_test/feature_requirements.rst b/bazel/rules/rules_score/test/fixtures/seooc_test/feature_requirements.rst new file mode 100644 index 0000000..d1be18a --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/seooc_test/static_architecture.rst b/bazel/rules/rules_score/test/fixtures/seooc_test/static_architecture.rst new file mode 100644 index 0000000..b81321c --- /dev/null +++ b/bazel/rules/rules_score/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/rules_score/test/fixtures/test_component_main.cc b/bazel/rules/rules_score/test/fixtures/test_component_main.cc new file mode 100644 index 0000000..578f2d5 --- /dev/null +++ b/bazel/rules/rules_score/test/fixtures/test_component_main.cc @@ -0,0 +1,13 @@ +// Main implementation for test_component +#include + +// Declarations from mock libraries +extern int mock_function_1(); +extern int mock_function_2(); + +int main(int argc, char** argv) { + std::cout << "Test Component Implementation" << std::endl; + std::cout << "Mock function 1 returns: " << mock_function_1() << std::endl; + std::cout << "Mock function 2 returns: " << mock_function_2() << std::endl; + return 0; +} diff --git a/bazel/rules/rules_score/test/fixtures/test_unit_test.cc b/bazel/rules/rules_score/test/fixtures/test_unit_test.cc new file mode 100644 index 0000000..3420f20 --- /dev/null +++ b/bazel/rules/rules_score/test/fixtures/test_unit_test.cc @@ -0,0 +1,25 @@ +// Unit tests for mock libraries +#include + +// Declarations from mock libraries +extern int mock_function_1(); +extern int mock_function_2(); + +int main() { + // Test mock_function_1 + int result1 = mock_function_1(); + if (result1 != 42) { + std::cerr << "Test failed: mock_function_1() returned " << result1 << ", expected 42" << std::endl; + return 1; + } + + // Test mock_function_2 + int result2 = mock_function_2(); + if (result2 != 84) { + std::cerr << "Test failed: mock_function_2() returned " << result2 << ", expected 84" << std::endl; + return 1; + } + + std::cout << "All tests passed!" << std::endl; + return 0; +} diff --git a/bazel/rules/rules_score/test/html_generation_test.bzl b/bazel/rules/rules_score/test/html_generation_test.bzl new file mode 100644 index 0000000..39f06cd --- /dev/null +++ b/bazel/rules/rules_score/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 sphinx_module HTML generation and dependencies.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("//bazel/rules/rules_score/private:sphinx_module.bzl", "SphinxModuleInfo", "SphinxNeedsInfo") + +# ============================================================================ +# Provider Tests +# ============================================================================ + +def _providers_test_impl(ctx): + """Test that sphinx_module provides the correct providers.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Verify required providers + asserts.true( + env, + SphinxModuleInfo in target_under_test, + "Target should provide SphinxModuleInfo", + ) + + 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[SphinxModuleInfo] + 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 sphinx_module generates needs.json files.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check for SphinxNeedsInfo 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 SphinxNeedsInfo provider + asserts.true( + env, + SphinxNeedsInfo in target_under_test, + "Needs target should provide SphinxNeedsInfo", + ) + + needs_info = target_under_test[SphinxNeedsInfo] + + # 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[SphinxModuleInfo] + + # 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[SphinxModuleInfo] + + # 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[SphinxModuleInfo] + + # 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[SphinxModuleInfo] + + # 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 sphinx_module_test_suite(name): + """Create a comprehensive test suite for sphinx_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/rules_score/test/score_module_providers_test.bzl b/bazel/rules/rules_score/test/score_module_providers_test.bzl new file mode 100644 index 0000000..24fba52 --- /dev/null +++ b/bazel/rules/rules_score/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 sphinx_module providers and two-phase build system.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("//bazel/rules/rules_score/private:sphinx_module.bzl", "SphinxModuleInfo", "SphinxNeedsInfo") + +# ============================================================================ +# SphinxModuleInfo Provider Tests +# ============================================================================ + +def _sphinx_module_info_fields_test_impl(ctx): + """Test that SphinxModuleInfo provides all required fields.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + asserts.true( + env, + SphinxModuleInfo in target_under_test, + "Target should provide SphinxModuleInfo", + ) + + score_info = target_under_test[SphinxModuleInfo] + + # Verify html_dir field + asserts.true( + env, + hasattr(score_info, "html_dir"), + "SphinxModuleInfo should have html_dir field", + ) + + asserts.true( + env, + score_info.html_dir != None, + "html_dir should not be None", + ) + + return analysistest.end(env) + +sphinx_module_info_fields_test = analysistest.make(_sphinx_module_info_fields_test_impl) + +# ============================================================================ +# SphinxNeedsInfo Provider Tests +# ============================================================================ + +def _score_needs_info_fields_test_impl(ctx): + """Test that SphinxNeedsInfo provides all required fields.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + asserts.true( + env, + SphinxNeedsInfo in target_under_test, + "Needs target should provide SphinxNeedsInfo", + ) + + needs_info = target_under_test[SphinxNeedsInfo] + + # Verify needs_json_file field (direct file) + asserts.true( + env, + hasattr(needs_info, "needs_json_file"), + "SphinxNeedsInfo 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"), + "SphinxNeedsInfo 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[SphinxNeedsInfo] + + # 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[SphinxNeedsInfo] + 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 SphinxNeedsInfo provider + asserts.true( + env, + SphinxNeedsInfo in target_under_test, + "Phase 1 should provide SphinxNeedsInfo", + ) + + # 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 SphinxModuleInfo provider + asserts.true( + env, + SphinxModuleInfo in target_under_test, + "Phase 2 should provide SphinxModuleInfo", + ) + + score_info = target_under_test[SphinxModuleInfo] + + # 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[SphinxModuleInfo] + + # 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[SphinxModuleInfo] + + # 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[SphinxModuleInfo] + + # 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[SphinxNeedsInfo] + 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 sphinx_module_providers_test_suite(name): + """Create a test suite for sphinx_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/rules_score/test/seooc_test.bzl b/bazel/rules/rules_score/test/seooc_test.bzl new file mode 100644 index 0000000..a88a637 --- /dev/null +++ b/bazel/rules/rules_score/test/seooc_test.bzl @@ -0,0 +1,135 @@ +""" +Test suite for dependable_element macro. + +Tests the SEooC (Safety Element out of Context) functionality including: +- Index generation with artifact references +- Integration with sphinx_module +- Sphinx-needs cross-referencing +- HTML output generation +""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("//bazel/rules/rules_score/private:sphinx_module.bzl", "SphinxModuleInfo", "SphinxNeedsInfo") + +def _seooc_index_generation_test_impl(ctx): + """Test that dependable_element generates proper index.rst file.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Get the generated index file + files = target_under_test[DefaultInfo].files.to_list() + + # 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, + index_file != None, + "Expected index.rst to be generated by dependable_element_index rule", + ) + + return analysistest.end(env) + +seooc_index_generation_test = analysistest.make( + impl = _seooc_index_generation_test_impl, +) + +def _seooc_artifacts_copied_test_impl(ctx): + """Test that all dependable element artifacts are copied to output directory.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + files = target_under_test[DefaultInfo].files.to_list() + + # Expected artifact basenames - these come from the SphinxSourcesInfo providers + # and are filtered to only include .rst/.md files for the index + expected_artifacts = [ + "component_requirements.rst", # from requirements + "dfa.rst", # from :dependability_analysis_target + ] + + # Check each artifact exists + actual_basenames = [f.basename for f in files] + for artifact in expected_artifacts: + asserts.true( + env, + artifact in actual_basenames, + "Expected artifact '{}' to be in output files".format(artifact), + ) + + return analysistest.end(env) + +seooc_artifacts_copied_test = analysistest.make( + impl = _seooc_artifacts_copied_test_impl, +) + +def _seooc_sphinx_module_generated_test_impl(ctx): + """Test that dependable_element generates sphinx_module with HTML output.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check that SphinxModuleInfo provider exists + asserts.true( + env, + SphinxModuleInfo in target_under_test, + "Expected dependable_element to provide SphinxModuleInfo from sphinx_module", + ) + + return analysistest.end(env) + +seooc_sphinx_module_generated_test = analysistest.make( + impl = _seooc_sphinx_module_generated_test_impl, +) + +def _seooc_needs_provider_test_impl(ctx): + """Test that dependable_element generates needs provider for cross-referencing.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check that SphinxNeedsInfo provider exists + asserts.true( + env, + SphinxNeedsInfo in target_under_test, + "Expected dependable_element_needs to provide SphinxNeedsInfo", + ) + + return analysistest.end(env) + +seooc_needs_provider_test = analysistest.make( + impl = _seooc_needs_provider_test_impl, +) + +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 generated index file + files = target_under_test[DefaultInfo].files.to_list() + + # Find index.rst + index_file = None + for f in files: + if f.basename == "index.rst": + index_file = f + break + + # 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, + index_file != None, + "Expected index.rst to exist for description validation", + ) + + return analysistest.end(env) + +seooc_description_test = analysistest.make( + impl = _seooc_description_test_impl, +) diff --git a/bazel/rules/rules_score/test/unit_component_test.bzl b/bazel/rules/rules_score/test/unit_component_test.bzl new file mode 100644 index 0000000..b756856 --- /dev/null +++ b/bazel/rules/rules_score/test/unit_component_test.bzl @@ -0,0 +1,258 @@ +# ******************************************************************************* +# 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 suite for unit, component, and dependable_element rules. + +Tests the new hierarchical structure for S-CORE process compliance: +- Unit: smallest testable element +- Component: collection of units +- Dependable Element: complete SEooC with full documentation +""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("//bazel/rules/rules_score:providers.bzl", "ComponentInfo", "DependableElementInfo", "SphinxSourcesInfo", "UnitInfo") + +# ============================================================================ +# Unit Tests +# ============================================================================ + +def _unit_provider_test_impl(ctx): + """Test that unit rule provides UnitInfo.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check UnitInfo provider exists + asserts.true( + env, + UnitInfo in target_under_test, + "Unit should provide UnitInfo", + ) + + unit_info = target_under_test[UnitInfo] + + # Verify fields are populated + asserts.true( + env, + unit_info.name != None, + "UnitInfo should have name field", + ) + + asserts.true( + env, + unit_info.unit_design != None, + "UnitInfo should have unit_design field", + ) + + asserts.true( + env, + unit_info.implementation != None, + "UnitInfo should have implementation field", + ) + + asserts.true( + env, + unit_info.tests != None, + "UnitInfo should have tests field", + ) + + return analysistest.end(env) + +unit_provider_test = analysistest.make(_unit_provider_test_impl) + +def _unit_sphinx_sources_test_impl(ctx): + """Test that unit rule provides SphinxSourcesInfo.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check SphinxSourcesInfo provider exists + asserts.true( + env, + SphinxSourcesInfo in target_under_test, + "Unit should provide SphinxSourcesInfo", + ) + + return analysistest.end(env) + +unit_sphinx_sources_test = analysistest.make(_unit_sphinx_sources_test_impl) + +# ============================================================================ +# Component Tests +# ============================================================================ + +def _component_provider_test_impl(ctx): + """Test that component rule provides ComponentInfo.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check ComponentInfo provider exists + asserts.true( + env, + ComponentInfo in target_under_test, + "Component should provide ComponentInfo", + ) + + comp_info = target_under_test[ComponentInfo] + + # Verify fields are populated + asserts.true( + env, + comp_info.name != None, + "ComponentInfo should have name field", + ) + + asserts.true( + env, + comp_info.requirements != None, + "ComponentInfo should have component_requirements field", + ) + + asserts.true( + env, + comp_info.units != None, + "ComponentInfo should have units field", + ) + + asserts.true( + env, + comp_info.tests != None, + "ComponentInfo should have tests field", + ) + + return analysistest.end(env) + +component_provider_test = analysistest.make(_component_provider_test_impl) + +def _component_sphinx_sources_test_impl(ctx): + """Test that component rule provides SphinxSourcesInfo.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check SphinxSourcesInfo provider exists + asserts.true( + env, + SphinxSourcesInfo in target_under_test, + "Component should provide SphinxSourcesInfo", + ) + + return analysistest.end(env) + +component_sphinx_sources_test = analysistest.make(_component_sphinx_sources_test_impl) + +# ============================================================================ +# Dependable Element Tests +# ============================================================================ + +def _dependable_element_provider_test_impl(ctx): + """Test that dependable_element rule provides DependableElementInfo.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check DependableElementInfo provider exists + asserts.true( + env, + DependableElementInfo in target_under_test, + "Dependable element should provide DependableElementInfo", + ) + + de_info = target_under_test[DependableElementInfo] + + # Verify fields are populated + asserts.true( + env, + de_info.name != None, + "DependableElementInfo should have name field", + ) + + asserts.true( + env, + de_info.description != None, + "DependableElementInfo should have description field", + ) + + asserts.true( + env, + de_info.assumptions_of_use != None, + "DependableElementInfo should have assumptions_of_use field", + ) + + asserts.true( + env, + de_info.requirements != None, + "DependableElementInfo should have requirements field", + ) + + asserts.true( + env, + de_info.architectural_design != None, + "DependableElementInfo should have architectural_design field", + ) + + asserts.true( + env, + de_info.dependability_analysis != None, + "DependableElementInfo should have dependability_analysis field", + ) + + asserts.true( + env, + de_info.consists_of != None, + "DependableElementInfo should have consists_of field", + ) + + asserts.true( + env, + de_info.tests != None, + "DependableElementInfo should have tests field", + ) + + return analysistest.end(env) + +dependable_element_provider_test = analysistest.make(_dependable_element_provider_test_impl) + +def _dependable_element_sphinx_sources_test_impl(ctx): + """Test that dependable_element rule provides SphinxSourcesInfo.""" + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + # Check SphinxSourcesInfo provider exists + asserts.true( + env, + SphinxSourcesInfo in target_under_test, + "Dependable element should provide SphinxSourcesInfo", + ) + + return analysistest.end(env) + +dependable_element_sphinx_sources_test = analysistest.make(_dependable_element_sphinx_sources_test_impl) + +# ============================================================================ +# Test Suite Definition +# ============================================================================ + +def unit_component_test_suite(name): + """Create test suite for unit, component, and dependable_element rules. + + Args: + name: Name of the test suite + """ + native.test_suite( + name = name, + tests = [ + ":unit_provider_test", + ":unit_sphinx_sources_test", + ":component_provider_test", + ":component_sphinx_sources_test", + ":dependable_element_provider_test", + ":dependable_element_sphinx_sources_test", + ], + )