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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 39 additions & 6 deletions bazel/rules/rules_score/private/architectural_design.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,34 @@ def _parse_puml_diagrams(ctx, files):
lobster_outputs.append(lobster)
return fbs_outputs, lobster_outputs

# Allowed file extensions per attribute.
# static/dynamic: diagram sources (.puml/.plantuml) and documentation (.rst/.md).
# public_api: diagram sources only — lobster traceability requires parseable diagrams.
_ALLOWED_EXTENSIONS = {
"static": ["puml", "plantuml", "rst", "md"],
"dynamic": ["puml", "plantuml", "rst", "md"],
"public_api": ["puml", "plantuml"],
}

def _validate_file_extensions(ctx):
"""Fail early if any resolved file has an unsupported extension.

Works for both direct file labels and filegroup contents because
ctx.files.* flattens everything to individual File objects.
"""
for attr_name, allowed in _ALLOWED_EXTENSIONS.items():
for f in getattr(ctx.files, attr_name):
if f.extension not in allowed:
fail(
"File '{}' passed to attribute '{}' of target '{}' has " +
"unsupported extension '.{}'. Allowed extensions: {}",
f.short_path,
attr_name,
ctx.label,
f.extension,
[".{}".format(e) for e in allowed],
)

def _architectural_design_impl(ctx):
"""Implementation for architectural_design rule.

Expand All @@ -104,6 +132,8 @@ def _architectural_design_impl(ctx):
List of providers including DefaultInfo, ArchitecturalDesignInfo, SphinxSourcesInfo
"""

_validate_file_extensions(ctx)

# Parse static and dynamic diagrams separately so each provider field
# carries the flatbuffers for its own category
static_fbs_list, static_lobster_list = _parse_puml_diagrams(ctx, ctx.files.static)
Expand Down Expand Up @@ -172,17 +202,17 @@ _architectural_design = rule(
"Automatically parses PlantUML files to produce FlatBuffers binary representations.",
attrs = {
"static": attr.label_list(
allow_files = [".puml", ".plantuml", ".svg", ".rst", ".md"],
allow_files = True,
mandatory = False,
doc = "Static architecture diagrams (class diagrams, component diagrams, etc.)",
doc = "Static architecture diagrams. Accepts direct file labels, filegroups, or any target whose files include .puml/.plantuml (parsed to FlatBuffers) or .rst/.md (documentation).",
),
"dynamic": attr.label_list(
allow_files = [".puml", ".plantuml", ".svg", ".rst", ".md"],
allow_files = True,
mandatory = False,
doc = "Dynamic architecture diagrams (sequence diagrams, activity diagrams, etc.)",
doc = "Dynamic architecture diagrams. Accepts direct file labels, filegroups, or any target whose files include .puml/.plantuml (parsed to FlatBuffers) or .rst/.md (documentation).",
),
"public_api": attr.label_list(
allow_files = [".puml", ".plantuml"],
allow_files = True,
mandatory = False,
doc = "Public API diagrams (parsed identically to static/dynamic). " +
"Classified separately so their lobster items are exposed via " +
Expand Down Expand Up @@ -213,7 +243,8 @@ def architectural_design(
static = [],
dynamic = [],
public_api = [],
visibility = None):
visibility = None,
tags = []):
"""Define architectural design following S-CORE process guidelines.

Architectural design documents describe the software architecture of a
Expand All @@ -237,6 +268,7 @@ def architectural_design(
diagrams but classified separately so their lobster items are
exposed via public_api_lobster_files, enabling failure-mode-to-
interface traceability at the dependable element level.
tags: List of Bazel tags to apply to the generated target.
visibility: Bazel visibility specification for the generated targets.

Generated Targets:
Expand Down Expand Up @@ -265,4 +297,5 @@ def architectural_design(
dynamic = dynamic,
public_api = public_api,
visibility = visibility,
tags = tags,
)
3 changes: 2 additions & 1 deletion bazel/rules/rules_score/private/dependable_element.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,8 @@ _dependable_element_index = rule(
),
"architectural_design": attr.label_list(
mandatory = True,
doc = "Architectural design targets or files.",
providers = [ArchitecturalDesignInfo],
doc = "Architectural design targets (architectural_design rule only).",
),
"dependability_analysis": attr.label_list(
mandatory = True,
Expand Down
62 changes: 62 additions & 0 deletions bazel/rules/rules_score/test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ load(
)
load(
":seooc_test.bzl",
"arch_design_invalid_extension_test",
"seooc_artifacts_copied_test",
"seooc_description_test",
"seooc_filegroup_arch_design_test",
"seooc_index_generation_test",
"seooc_needs_provider_test",
"seooc_sphinx_module_generated_test",
Expand Down Expand Up @@ -183,6 +185,31 @@ architectural_design(
static = ["fixtures/test_dependable_element_nested.puml"],
)

# Filegroup fixture: a plain RST file wrapped in a filegroup, used to test
# that architectural_design.static accepts filegroup targets (not just direct file labels).
filegroup(
name = "static_arch_filegroup",
srcs = ["fixtures/seooc_test/static_architecture.rst"],
)

# architectural_design that mixes a filegroup (RST docs) and a direct .puml file.
# The .puml provides the SEooC declaration with no components (matching components=[]);
# the filegroup exercises filegroup support.
architectural_design(
name = "arch_design_with_filegroup",
static = [
":static_arch_filegroup",
"fixtures/test_de_filegroup_arch_design.puml",
],
)

# Filegroup with an unsupported file extension (.cc) — used by the failure test
# that verifies architectural_design rejects bad file types at analysis time.
filegroup(
name = "invalid_ext_filegroup",
srcs = ["fixtures/mock_lib1.cc"],
)

# - Safety Analysis (DFA): wp__sw_component_dfa
# - Safety Analysis (FMEA): wp__sw_component_fmea
dependability_analysis(
Expand Down Expand Up @@ -414,6 +441,21 @@ dependable_element(
tests = [],
)

# Dependable element that passes a filegroup into architectural_design via a wrapper
# architectural_design target. Exercises the filegroup-in-static path.
dependable_element(
name = "test_de_filegroup_arch_design",
testonly = True,
architectural_design = [":arch_design_with_filegroup"],
assumptions_of_use = [":aous"],
components = [],
dependability_analysis = [":dependability_analysis_target"],
description = "Test dependable element using a filegroup inside architectural_design.",
integrity_level = "B",
requirements = [":feat_req"],
tests = [],
)

# ============================================================================
# Test Instantiations - HTML Generation Tests
# ============================================================================
Expand Down Expand Up @@ -506,6 +548,24 @@ seooc_description_test(
target_under_test = ":seooc_test_lib_index",
)

seooc_filegroup_arch_design_test(
name = "seooc_filegroup_arch_design_test",
target_under_test = ":test_de_filegroup_arch_design_index",
)

# Failure test: architectural_design must reject files with unsupported extensions
# even when they arrive via a filegroup.
architectural_design(
name = "arch_design_invalid_ext",
static = [":invalid_ext_filegroup"],
tags = ["manual"],
)

arch_design_invalid_extension_test(
name = "arch_design_invalid_extension_test",
target_under_test = ":arch_design_invalid_ext",
)

# ============================================================================
# Test Suites
# ============================================================================
Expand Down Expand Up @@ -570,6 +630,8 @@ sphinx_module_providers_test_suite(name = "sphinx_module_providers_tests")
test_suite(
name = "seooc_tests",
tests = [
":arch_design_invalid_extension_test",
":seooc_filegroup_arch_design_test",
":seooc_tests_artifacts_copied",
":seooc_tests_description",
":seooc_tests_index_generation",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
' *******************************************************************************
' Copyright (c) 2026 Contributors to the Eclipse Foundation
'
' See the NOTICE file(s) distributed with this work for additional
' information regarding copyright ownership.
'
' This program and the accompanying materials are made available under the
' terms of the Apache License Version 2.0 which is available at
' https://www.apache.org/licenses/LICENSE-2.0
'
' SPDX-License-Identifier: Apache-2.0
' *******************************************************************************

@startuml test_de_filegroup_arch_design

package "Test DE Filegroup Arch Design" as test_de_filegroup_arch_design <<SEooC>> {
}

@enduml
51 changes: 51 additions & 0 deletions bazel/rules/rules_score/test/seooc_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,54 @@ def _seooc_description_test_impl(ctx):
seooc_description_test = analysistest.make(
impl = _seooc_description_test_impl,
)

def _seooc_filegroup_arch_design_test_impl(ctx):
"""Test that dependable_element works when a filegroup is used inside
architectural_design.static.

Verifies:
- index.rst is generated
- The RST file contributed by the filegroup is present in the output files
under the architectural_design/ directory.
"""
env = analysistest.begin(ctx)
target_under_test = analysistest.target_under_test(env)

files = target_under_test[DefaultInfo].files.to_list()
basenames = [f.basename for f in files]

asserts.true(
env,
"index.rst" in basenames,
"Expected index.rst to be generated when architectural_design contains a filegroup",
)

# The filegroup contributes static_architecture.rst; it must appear in outputs
# symlinked under the architectural_design/ subtree.
arch_rst_files = [f for f in files if f.basename == "static_architecture.rst"]
asserts.true(
env,
len(arch_rst_files) > 0,
"Expected static_architecture.rst (from filegroup) to be in output files under architectural_design/",
)

return analysistest.end(env)

seooc_filegroup_arch_design_test = analysistest.make(
impl = _seooc_filegroup_arch_design_test_impl,
)

def _arch_design_invalid_extension_test_impl(ctx):
"""Test that architectural_design fails with a clear error when a filegroup
contains a file with an unsupported extension (e.g. .cc)."""
env = analysistest.begin(ctx)
asserts.expect_failure(
env,
"unsupported extension",
)
return analysistest.end(env)

arch_design_invalid_extension_test = analysistest.make(
impl = _arch_design_invalid_extension_test_impl,
expect_failure = True,
)
Loading